fix: XSS escaping, null guards, JS bug fixes, and documentation updates

- Escape $serverObject and $systemUrl in AdminHTML.php heredocs to prevent XSS
- Add null guard in Database::getSystemUrl() to prevent fatal error
- Guard primaryNetwork access in module.js to prevent null dereference
- Reset badge/traffic-bar CSS classes on refresh to prevent accumulation
- Add VNC popup-blocked check with user-facing message
- Add BS3 input-group-btn dual class for theme compatibility
- Escape billing template variables with |escape:'htmlall'
- Add cache-busting to admin CSS/JS includes
- Switch cache-busting format from version to date-based (20260207)
- Create .releaserc.json for automated CHANGELOG.md management
- Add changelog/git plugins to semantic-release workflow
- Remove manual [Unreleased] section from CHANGELOG.md
- Update README: install/upgrade with rsync, accuracy fixes, add keygen.js
- Update CLAUDE.md: add keygen.js, document removed features
- Fix SECURITY.md grammar and version operator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
EZSCALE
2026-02-07 15:48:49 -06:00
parent e73e85c5a9
commit 6c7cdc6421
11 changed files with 91 additions and 115 deletions

View File

@@ -139,7 +139,7 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) {
$systemUrl = Database::getSystemUrl();
return "
<script src=\"" . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . "modules/servers/VirtFusionDirect/templates/js/keygen.js?v=0.0.20\"></script>
<script src=\"" . htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8') . "modules/servers/VirtFusionDirect/templates/js/keygen.js?v=20260207\"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var osTemplates = " . json_encode($dropdownOptions, JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";

View File

@@ -7,6 +7,7 @@ class AdminHTML
public static function options($systemUrl, $serviceId)
{
$systemUrl = htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8');
return <<<EOT
<button onclick="impersonateServerOwner('${serviceId}', '${systemUrl}')" type="button" class="btn btn-primary">Impersonate Server Owner</button>
<span class="text-info">&nbsp;&nbsp;A valid VirtFusion admin session in the same browser is required for this functionality to work.</span>
@@ -15,6 +16,7 @@ EOT;
public static function serverObject($serverObject)
{
$serverObject = htmlspecialchars($serverObject, ENT_QUOTES, 'UTF-8');
return <<<EOT
<textarea class="form-control" name="modulefields[1]" rows="10" style="width: 100%" disabled>${serverObject}</textarea>
EOT;
@@ -31,9 +33,10 @@ EOT;
public static function serverInfo($systemUrl, $serviceId)
{
$systemUrl = htmlspecialchars($systemUrl, ENT_QUOTES, 'UTF-8');
return <<<EOT
<link href="${systemUrl}modules/servers/VirtFusionDirect/templates/css/module.css" rel="stylesheet">
<script src="${systemUrl}modules/servers/VirtFusionDirect/templates/js/module.js"></script>
<link href="${systemUrl}modules/servers/VirtFusionDirect/templates/css/module.css?v=20260207" rel="stylesheet">
<script src="${systemUrl}modules/servers/VirtFusionDirect/templates/js/module.js?v=20260207"></script>
<div id="vf-loader" class="vf-loader">
<div id="vf-loading"></div>
</div>

View File

@@ -54,6 +54,7 @@ class Database
public static function getSystemUrl()
{
$url = DB::table('tblconfiguration')->where('setting', '=', 'SystemURL')->first();
if (!$url) return '';
return $url->value;
}

View File

@@ -25,13 +25,15 @@ function vfServerData(serviceId, systemUrl) {
$("#vf-data-server-traffic-used").text(response.data.trafficUsed || "-");
$("#vf-data-server-storage").text(response.data.storage);
$("#vf-data-server-cpu").text(response.data.cpu);
$("#vf-data-server-ipv4").text(response.data.primaryNetwork.ipv4);
$("#vf-data-server-ipv6").text(response.data.primaryNetwork.ipv6);
var pn = response.data.primaryNetwork || {};
$("#vf-data-server-ipv4").text(pn.ipv4 || "-");
$("#vf-data-server-ipv6").text(pn.ipv6 || "-");
// Update status badge
var statusBadge = $("#vf-status-badge");
var status = (response.data.status || "unknown").toLowerCase();
statusBadge.text(status.charAt(0).toUpperCase() + status.slice(1));
statusBadge.removeClass("vf-badge-active vf-badge-suspended vf-badge-awaiting");
if (status === "active" || status === "running") {
statusBadge.addClass("vf-badge-active");
} else if (status === "suspended") {
@@ -56,7 +58,7 @@ function vfServerData(serviceId, systemUrl) {
if (trafficTotal > 0) {
$("#vf-res-traffic").text(trafficUsed + " / " + trafficTotal + " GB");
var pct = Math.min(100, Math.round((trafficUsed / trafficTotal) * 100));
$("#vf-res-traffic-bar").css("width", pct + "%");
$("#vf-res-traffic-bar").css("width", pct + "%").removeClass("bg-danger bg-warning");
if (pct > 90) {
$("#vf-res-traffic-bar").addClass("bg-danger");
} else if (pct > 70) {
@@ -140,8 +142,9 @@ function vfServerDataAdmin(serviceId, systemUrl) {
$("#vf-data-server-traffic").text(response.data.traffic);
$("#vf-data-server-storage").text(response.data.storage);
$("#vf-data-server-cpu").text(response.data.cpu);
$("#vf-data-server-ipv4").text(response.data.primaryNetwork.ipv4);
$("#vf-data-server-ipv6").text(response.data.primaryNetwork.ipv6);
var pnAdmin = response.data.primaryNetwork || {};
$("#vf-data-server-ipv4").text(pnAdmin.ipv4 || "-");
$("#vf-data-server-ipv6").text(pnAdmin.ipv6 || "-");
$("#vf-server-info").show();
} else {
$("#vf-server-info-error").show();
@@ -383,6 +386,14 @@ function vfOpenVnc(serviceId, systemUrl) {
// Open window immediately in click context to avoid popup blockers
var vncWindow = window.open("", "_blank");
if (!vncWindow) {
alertDiv.removeClass("alert-success").addClass("alert-danger");
alertDiv.text("Popup blocked. Please allow popups for this site and try again.");
alertDiv.show();
spinner.hide();
btn.prop("disabled", false);
return;
}
$.ajax({
type: "GET",

View File

@@ -1,5 +1,5 @@
<link href="{$systemURL}modules/servers/VirtFusionDirect/templates/css/module.css?v=0.0.20" rel="stylesheet">
<script src="{$systemURL}modules/servers/VirtFusionDirect/templates/js/module.js?v=0.0.20"></script>
<link href="{$systemURL}modules/servers/VirtFusionDirect/templates/css/module.css?v=20260207" rel="stylesheet">
<script src="{$systemURL}modules/servers/VirtFusionDirect/templates/js/module.js?v=20260207"></script>
{if $serviceStatus eq 'Active'}
@@ -274,7 +274,7 @@
<h5 class="vf-bold">Add Credit</h5>
<div class="input-group mb-2">
<input type="number" id="vf-ss-credit-amount" class="form-control" placeholder="Amount" min="1" step="1">
<div class="input-group-append">
<div class="input-group-append input-group-btn">
<button id="vf-ss-add-credit-btn" onclick="vfAddCredit('{$serviceid}','{$systemURL}')" type="button" class="btn btn-primary">
<span id="vf-ss-add-credit-spinner" class="spinner-border spinner-border-sm" style="display:none;"></span>
Add Credit
@@ -328,29 +328,29 @@
<div class="col-lg-6">
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">Product:</div>
<div class="col-xs-6 col-6">{$groupname} - {$product}</div>
<div class="col-xs-6 col-6">{$groupname|escape:'htmlall'} - {$product|escape:'htmlall'}</div>
</div>
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">{$LANG.recurringamount}:</div>
<div class="col-xs-6 col-6">{$recurringamount}</div>
<div class="col-xs-6 col-6">{$recurringamount|escape:'htmlall'}</div>
</div>
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">{$LANG.orderbillingcycle}:</div>
<div class="col-xs-6 col-6">{$billingcycle}</div>
<div class="col-xs-6 col-6">{$billingcycle|escape:'htmlall'}</div>
</div>
</div>
<div class="col-lg-6">
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">{$LANG.clientareahostingregdate}:</div>
<div class="col-xs-6 col-6">{$regdate}</div>
<div class="col-xs-6 col-6">{$regdate|escape:'htmlall'}</div>
</div>
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">{$LANG.clientareahostingnextduedate}:</div>
<div class="col-xs-6 col-6">{$nextduedate}</div>
<div class="col-xs-6 col-6">{$nextduedate|escape:'htmlall'}</div>
</div>
<div class="row p-2">
<div class="col-xs-6 col-6 text-right vf-bold">{$LANG.orderpaymentmethod}:</div>
<div class="col-xs-6 col-6">{$paymentmethod}</div>
<div class="col-xs-6 col-6">{$paymentmethod|escape:'htmlall'}</div>
</div>
</div>
</div>