Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
651af90
Harden API v3 and CLI: fail-closed auth, sync data-loss guards, DRY s…
claude Aug 16, 2026
e5ee3fb
Fix legacy (pre-API) security and data-integrity defects
claude Aug 16, 2026
a4a5bfb
Fix reporting screens and the remaining legacy defect tail
claude Aug 16, 2026
a545878
Fix regressions found by adversarial review of the previous commits
claude Aug 16, 2026
fbddac1
Fix the remaining findings from the multi-agent review (D2-D19)
claude Aug 16, 2026
f31efe6
Reclassify rotator/webhook findings as intra-install, not cross-tenant
claude Aug 16, 2026
f026c13
LTV: compute real per-product aov/repeat_rate/mrr for /ltv/predict?by…
claude Aug 16, 2026
a4d6c72
API: revoke deleted users' access on every version; close customers_a…
claude Aug 17, 2026
c7c851d
Review front-end JS, Go CLI, mobile templates and Docker infra
claude Aug 17, 2026
e50192e
Fix credential-store, sync-lock and API-client defects in the Go CLI
claude Aug 17, 2026
9b72e7e
Validate positional IDs and keep destructive-command output off stdout
claude Aug 17, 2026
920a194
Stop silently reporting zeros and empty output when parsing or encodi…
claude Aug 17, 2026
216bd66
Make diff comparison fail toward "changed" when encoding fails
claude Aug 17, 2026
4802890
Consolidate the five bulk/single delete implementations onto one runner
claude Aug 17, 2026
8ec5402
Complete the line-by-line pass: fix misreported fallbacks and fail-op…
claude Aug 17, 2026
412b399
Merge master: reconcile the review work with conformal/ensemble forec…
claude Sep 4, 2026
b92036e
Fix six defects found by code review of this branch
claude Sep 4, 2026
e5d152b
Contain NaN propagation in the merged forecasting engine
claude Sep 4, 2026
6776359
Fix two review findings: orphaned migrated profile, non-durable atomi…
claude Sep 4, 2026
bf0e74c
Merge master (#147): integrate agent-safe writes with the delete cons…
claude Sep 4, 2026
99fc5e3
Fix five Go CLI review findings, two of them by structural check
claude Sep 4, 2026
166a3ba
Close the remaining PHP review findings: masking, transactions, SSRF …
claude Sep 4, 2026
87bdd20
Make transaction() the only transaction primitive; fix tracker-edit d…
claude Sep 7, 2026
f5e49cb
Replace the line-regex source scanners with one token-based helper; a…
claude Sep 7, 2026
09555e2
Consolidate campaign-data masking into Prosper202\Report\CampaignData…
claude Sep 7, 2026
734f592
One outbound-URL guard: shape at write boundaries, full check plus pi…
claude Sep 7, 2026
c4c1b10
Connection: a failed fetch is an error, not an empty answer; Messagin…
claude Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion 202-Mobile/202-login.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@
$login_server_serialized,
$login_session_serialized
);
$log_stmt->execute();
if (!$log_stmt->execute()) {
prosper_log('login', 'Unable to write mobile login log row: ' . $log_stmt->error);
}
$log_stmt->close();
} elseif ($should_log_attempt) {
prosper_log('login', 'Unable to prepare mobile login log statement: ' . $db->error);
Expand Down
8 changes: 6 additions & 2 deletions 202-Mobile/mini-stats/202-ministats.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
//grab the users date range preferences
$time = grab_timeframe();
$click_filtered = '';
$mysql['to'] = $db->real_escape_string($time['to']);
$mysql['from'] = $db->real_escape_string($time['from']);
// grab_timeframe() returns int timestamps (mktime/time), and this file declares
// strict_types=1 — passing an int to real_escape_string(string) is a TypeError,
// not a coercion, so the mobile mini-stats page fataled for every account whose
// time preference resolves to a computed window (the default, 'today').
$mysql['to'] = $db->real_escape_string((string)$time['to']);
$mysql['from'] = $db->real_escape_string((string)$time['from']);


//show real or filtered clicks
Expand Down
64 changes: 49 additions & 15 deletions 202-account/account.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
$mysql['user_own_id'] = $db->real_escape_string((string)$_SESSION['user_own_id']);
$user_sql = "SELECT 2u.user_name as username, 2up.user_slack_incoming_webhook AS url FROM 202_users AS 2u INNER JOIN 202_users_pref AS 2up ON (2up.user_id = 1) WHERE 2u.user_id = '" . $mysql['user_own_id'] . "'";
$user_results = $db->query($user_sql);
$user_row = $user_results->fetch_assoc();
$user_row = $user_results ? ($user_results->fetch_assoc() ?: []) : [];
$username = $user_row['username'];

if (!empty($user_row['url']))
Expand Down Expand Up @@ -89,16 +89,20 @@
die();
}

// Inbound handoff from my.tracking202.com, which redirects here with the key
// base64'd in the query string. A GET must NOT change state: the vendor cannot
// carry our session token, so this used to be an unauthenticated-origin write —
// any site could <img src="...account.php?customers_api_key=..."> and silently
// rewrite the account's customer API key. Decode it, hold it, and let the user
// confirm through the token-checked POST handler below (which performs the same
// validation and write, so there is no second code path to keep in sync).
$pendingCustomerApiKey = null;
if (!empty($_GET['customers_api_key'])) {
$mysql['p202_customer_api_key'] = $db->real_escape_string(base64_decode((string) $_GET['customers_api_key']));
$mysql['user_id'] = $db->real_escape_string((string)$_SESSION['user_own_id']);
$validate = validateCustomersApiKey($mysql['p202_customer_api_key']);
if ($validate['code'] != 200) {
$error['p202_customer_api_key_invalid'] = "API key is not valid. Check your key and try again!";
}
if (!$error) {
$db->query("UPDATE 202_users SET p202_customer_api_key = '" . $mysql['p202_customer_api_key'] . "' WHERE user_id = '" . $mysql['user_id'] . "'");
$change_p202_customer_api_key = true;
$decodedCustomerApiKey = base64_decode((string) $_GET['customers_api_key'], true);
if ($decodedCustomerApiKey === false || trim($decodedCustomerApiKey) === '') {
$error['p202_customer_api_key_invalid'] = 'That API key link was malformed. Copy your key from my.tracking202.com and paste it in the field below.';
} else {
$pendingCustomerApiKey = trim($decodedCustomerApiKey);
}
}

Expand Down Expand Up @@ -148,7 +152,7 @@
}

$user_result = $db->query($user_sql);
$user_row = $user_result->fetch_assoc();
$user_row = $user_result ? ($user_result->fetch_assoc() ?: []) : [];
$currentUserEmail = isset($user_row['user_email']) ? (string)$user_row['user_email'] : '';
$html = array_map('htmlentities', $user_row);

Expand Down Expand Up @@ -487,7 +491,12 @@
}

if (!empty($_POST['change_user_stats202_app_key']) && $_POST['change_user_stats202_app_key'] == '1') {
if (!preg_match('/\*/', (string) $_POST['user_stats202_app_key'])) {
// CSRF check — every other mutation block in this file validates the token;
// this one omitted it, letting a forged form overwrite the Stats202 app key.
if (!hash_equals((string)($_SESSION['token'] ?? ''), (string)($_POST['token'] ?? ''))) {
$error['token'] = 'You must use our forms to submit data.';
}
if (!$error && !preg_match('/\*/', (string) $_POST['user_stats202_app_key'])) {
// Replace the undefined method with a more direct validation approach
$app_key = $_POST['user_stats202_app_key'];
$api_key = $_SESSION['user_api_key'];
Expand Down Expand Up @@ -515,12 +524,15 @@
}

if (!empty($_POST['update_p202_customer_api_key']) && $_POST['update_p202_customer_api_key'] == '1') {
// Check the token BEFORE validateCustomersApiKey(): that makes a server-side
// call out to the vendor with the submitted value, so validating first let a
// forged request drive outbound traffic even though the write was blocked.
if (!hash_equals((string)($_SESSION['token'] ?? ''), (string)($_POST['token'] ?? ''))) {
$error['token'] = 'You must use our forms to submit data.';
}
$mysql['p202_customer_api_key'] = $db->real_escape_string((string)$_POST['p202_customer_api_key']);
$mysql['user_id'] = $db->real_escape_string((string)$_SESSION['user_own_id']);
$validate = validateCustomersApiKey($_POST['p202_customer_api_key']);
$validate = $error ? ['code' => 0] : validateCustomersApiKey($_POST['p202_customer_api_key']);
if ($validate['code'] != 200 && $mysql['p202_customer_api_key'] != '') {
$error['p202_customer_api_key_invalid'] = "API key is not valid. Check your key and try again!";
}
Expand Down Expand Up @@ -562,7 +574,7 @@
$verify_stmt->bind_param('i', $current_user_id);
$verify_stmt->execute();
$result = $verify_stmt->get_result();
$stored = $result ? $result->fetch_assoc() : null;
$stored = $result ? ($result->fetch_assoc() ?: []) : [];
$verify_stmt->close();
if (!$stored || !verify_user_pass((string) $_POST['user_pass'], (string) ($stored['user_pass'] ?? ''))['valid']) {
$error['user_pass'] .= 'Your old password was typed incorrectly.';
Expand Down Expand Up @@ -637,7 +649,7 @@
LEFT JOIN `202_users_pref` USING (user_id)
WHERE `202_users`.`user_id`='" . $mysql['user_id'] . "'";
$user_result = $db->query($user_sql);
$user_row = $user_result->fetch_assoc();
$user_row = $user_result ? ($user_result->fetch_assoc() ?: []) : [];
$html = array_map('htmlentities', $user_row);
?>

Expand Down Expand Up @@ -677,6 +689,28 @@
<?php if ($change_p202_customer_api_key) { ?>
<div class="success" style="text-align:right"><small><span class="fui-check-inverted"></span> Your submission was successful. Your Prosper202 customer API key have been saved.</small></div>
<?php } ?>

<?php if ($pendingCustomerApiKey !== null) {
// Confirmation step for the vendor handoff above. Submits through
// update_p202_customer_api_key, which checks the session token.
$pendingPreview = strlen($pendingCustomerApiKey) > 8
? substr($pendingCustomerApiKey, 0, 8) . str_repeat('*', 12)
: $pendingCustomerApiKey;
?>
<div class="alert" style="text-align:left; padding:10px; border:1px solid #e5e5e5; margin-bottom:10px;">
<form method="post" action="" class="form-inline" role="form">
<input type="hidden" name="update_p202_customer_api_key" value="1" />
<input type="hidden" name="token" value="<?php echo htmlspecialchars((string) ($_SESSION['token'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" />
<input type="hidden" name="p202_customer_api_key" value="<?php echo htmlspecialchars($pendingCustomerApiKey, ENT_QUOTES, 'UTF-8'); ?>" />
<small>
Connect the Prosper202 customer API key
<code><?php echo htmlspecialchars($pendingPreview, ENT_QUOTES, 'UTF-8'); ?></code>
to this account?
</small>
<button class="btn btn-sm btn-p202" type="submit" style="margin-left:8px;">Save API key</button>
</form>
</div>
<?php } ?>
</div>
</div>
</div>
Expand Down
15 changes: 12 additions & 3 deletions 202-account/administration.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@

AUTH::require_user();

// The Settings nav link is already gated on access_to_settings
// (202-config/template.php); the page itself must enforce the same
// permission, or any authenticated low-privilege user can POST directly
// to the install-wide settings and click-data deletion actions below.
if (!$userObj->hasPermission("access_to_settings")) {
header('location: ' . get_absolute_url() . '202-account/');
exit;
}

$slack = false;
$mysql['user_own_id'] = $db->real_escape_string((string) $_SESSION['user_own_id']);
$user_sql = "SELECT 2u.user_name as username, 2up.user_slack_incoming_webhook AS url, maxmind_isp, user_time_register, 2up.user_auto_database_optimization_days FROM 202_users AS 2u INNER JOIN 202_users_pref AS 2up ON (2up.user_id = 1) WHERE 2u.user_id = '" . $mysql['user_own_id'] . "'";
$user_results = $db->query($user_sql);
$user_row = $user_results->fetch_assoc();
$user_row = $user_results ? ($user_results->fetch_assoc() ?: []) : [];
$username = $user_row['username'];
$user_time_register = $user_row['user_time_register'];

Expand All @@ -34,9 +43,9 @@

$de_query = "SELECT count(*) as total, sum(processed) as done FROM 202_dataengine_job";
$de_result = $db->query($de_query);
$de_row = $de_result->fetch_assoc();
$de_row = $de_result ? ($de_result->fetch_assoc() ?: []) : [];

if ($de_result->num_rows && $de_row['total'] != 0) {
if ($de_result && $de_result->num_rows && $de_row['total'] != 0) {
$de_total = $de_row['total'];
$de_done = $de_row['done'];
$de_ratio = @round(($de_done / $de_total) * 100, 2);
Expand Down
6 changes: 6 additions & 0 deletions 202-account/ajax/validate-apikey.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<?php
declare(strict_types=1);
include_once(str_repeat("../", 2).'202-config/connect.php');

// Every other endpoint in 202-account/ajax/ authenticates. Without this,
// api_key_validate() makes a server-side cURL POST of attacker-supplied
// input to the vendor API — an unauthenticated outbound-request oracle.
AUTH::require_user();

if (isset($_POST['apikey'])) {
echo api_key_validate($_POST['apikey']);
}
Expand Down
31 changes: 20 additions & 11 deletions 202-account/api-integrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function lpo_ctx_pref_cache_bust($userId)
$mysql['user_own_id'] = $db->real_escape_string((string)$_SESSION['user_own_id']);
$user_sql = "SELECT 2u.user_name as username, 2up.user_slack_incoming_webhook AS url, 2u.install_hash, 2u.p202_customer_api_key FROM 202_users AS 2u INNER JOIN 202_users_pref AS 2up ON (2up.user_id = 1) WHERE 2u.user_id = '" . $mysql['user_own_id'] . "'";
$user_results = $db->query($user_sql);
$user_row = $user_results->fetch_assoc();
$user_row = $user_results ? ($user_results->fetch_assoc() ?: []) : [];
$username = $user_row['username'];
$editing_dni_network = false;
$dniNetworks = getAllDniNetworks($user_row['install_hash']);
Expand All @@ -180,7 +180,7 @@ function lpo_ctx_pref_cache_bust($userId)
FROM 202_users_pref
WHERE user_id='" . $mysql['user_id'] . "'";
$user_results = $db->query($user_sql);
$user_row = $user_results->fetch_assoc();
$user_row = $user_results ? ($user_results->fetch_assoc() ?: []) : [];
if ($user_row['cb_verified']) {
echo '<span class="label label-primary">Verified</span>';
} else {
Expand All @@ -196,7 +196,7 @@ function lpo_ctx_pref_cache_bust($userId)
LEFT JOIN `202_users_pref` USING (user_id)
WHERE `202_users`.`user_id`='" . $mysql['user_id'] . "'";
$user_result = $db->query($user_sql);
$user_row = $user_result->fetch_assoc();
$user_row = $user_result ? ($user_result->fetch_assoc() ?: []) : [];
$html = array_map('htmlentities', $user_row);

$cb_verified = $user_row['cb_verified'];
Expand Down Expand Up @@ -467,8 +467,17 @@ function lpo_ctx_pref_cache_bust($userId)
}


if (isset($_GET['delete_dni_network']) && !empty($_GET['delete_dni_network'])) {
$mysql['deleteDniNetworkId'] = $db->real_escape_string((string)$_GET['delete_dni_network']);
// Deleting is a POST: a GET carrying the CSRF token put that token into browser
// history, Referer headers and access logs, and it guards every POST mutation in
// the session.
if (isset($_POST['delete_dni_network']) && !empty($_POST['delete_dni_network'])) {
// CSRF check — this deletes a DNI network and marks the linked aff network
// deleted.
if (!hash_equals((string)($_SESSION['token'] ?? ''), (string)($_POST['token'] ?? ''))) {
http_response_code(403);
die('Invalid token.');
}
$mysql['deleteDniNetworkId'] = $db->real_escape_string((string)$_POST['delete_dni_network']);
$db->query("DELETE FROM 202_dni_networks WHERE id = '" . $mysql['deleteDniNetworkId'] . "' AND user_id = '" . $mysql['user_id'] . "'");
$sql = "UPDATE 202_aff_networks SET aff_network_deleted = '1', aff_network_time = '" . time() . "' WHERE dni_network_id = '" . $mysql['deleteDniNetworkId'] . "'";
$db->query($sql);
Expand Down Expand Up @@ -599,7 +608,7 @@ function lpo_ctx_pref_cache_bust($userId)
}
?>
<tr>
<td> <img src="<?php echo $dni_row['favIcon']; ?>" width=16>&nbsp;&nbsp;<?php echo $dni_row['name'] . " (" . $dni_row['type'] . ")"; ?><span class="fui-info-circle" style="font-size: 12px; margin: -25px 0px 0px 5px;" data-toggle="tooltip" title="" data-original-title="<?php echo $dni_row['shortDescription']; ?>"></span><br>
<td> <img src="<?php echo htmlspecialchars((string) ($dni_row['favIcon']), ENT_QUOTES, 'UTF-8'); ?>" width=16>&nbsp;&nbsp;<?php echo htmlspecialchars((string) ($dni_row['name']), ENT_QUOTES, 'UTF-8') . " (" . htmlspecialchars((string) ($dni_row['type']), ENT_QUOTES, 'UTF-8') . ")"; ?><span class="fui-info-circle" style="font-size: 12px; margin: -25px 0px 0px 5px;" data-toggle="tooltip" title="" data-original-title="<?php echo htmlspecialchars((string) ($dni_row['shortDescription']), ENT_QUOTES, 'UTF-8'); ?>"></span><br>
<?php if ($dni_row['processed'] == false) { ?>
<div id="network-<?php echo $dni_row['id']; ?>">
<span style='font-size:10px'>processing... <img src="<?php echo get_absolute_url(); ?>202-img/loader-small.gif"></span>
Expand All @@ -611,9 +620,9 @@ function lpo_ctx_pref_cache_bust($userId)
<div>
<?php } ?>
</td>
<td><?php echo substr((string) $dni_row['apiKey'], 0, 12) . "... "; ?><a href="#" class="link showFullDniApikey" data-long="<?php echo $dni_row['apiKey']; ?>" data-short="<?php echo substr((string) $dni_row['apiKey'], 0, 12); ?>">show</a></td>
<td><?php echo substr((string) $dni_row['apiKey'], 0, 12) . "... "; ?><a href="#" class="link showFullDniApikey" data-long="<?php echo htmlspecialchars((string) ($dni_row['apiKey']), ENT_QUOTES, 'UTF-8'); ?>" data-short="<?php echo substr((string) $dni_row['apiKey'], 0, 12); ?>">show</a></td>
<td><?php echo $dni_row['affiliateId']; ?></td>
<td><a href="<?php echo get_absolute_url(); ?>202-account/api-integrations.php?edit_dni_network=<?php echo $dni_row['id']; ?>" title="Edit"><i class="glyphicon glyphicon-pencil"></i></a> <a href="<?php echo get_absolute_url(); ?>202-account/api-integrations.php?delete_dni_network=<?php echo $dni_row['id']; ?>" onClick="return confirm('Delete This DNI Network?')" title="Delete"><i class="glyphicon glyphicon-trash"></i></a></td>
<td><a href="<?php echo get_absolute_url(); ?>202-account/api-integrations.php?edit_dni_network=<?php echo $dni_row['id']; ?>" title="Edit"><i class="glyphicon glyphicon-pencil"></i></a> <form method="post" style="display:inline" onsubmit="return confirm('Delete This DNI Network?');"><input type="hidden" name="token" value="<?php echo htmlspecialchars((string) ($_SESSION['token'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>"><input type="hidden" name="delete_dni_network" value="<?php echo htmlspecialchars((string) $dni_row['id'], ENT_QUOTES, 'UTF-8'); ?>"><button type="submit" title="Delete" class="btn btn-link" style="padding:0;border:0;vertical-align:baseline"><i class="glyphicon glyphicon-trash"></i></button></form></td>
</tr>
<?php } ?>
</tbody>
Expand All @@ -624,8 +633,8 @@ function lpo_ctx_pref_cache_bust($userId)
<div class="apiint-dni-form">
<form class="form-horizontal" role="form" method="post" action="">
<input type="hidden" name="token" value="<?php echo $_SESSION['token']; ?>">
<input type="hidden" name="dni_network_type" id="dni_network_type" value="<?php echo $edit_dni_row['type'] ?? ''; ?>">
<input type="hidden" name="dni_network_name" id="dni_network_name" value="<?php echo $edit_dni_row['name'] ?? ''; ?>">
<input type="hidden" name="dni_network_type" id="dni_network_type" value="<?php echo htmlspecialchars((string) ($edit_dni_row['type'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>">
<input type="hidden" name="dni_network_name" id="dni_network_name" value="<?php echo htmlspecialchars((string) ($edit_dni_row['name'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>">
<?php if (isset($editing_dni_network) && $editing_dni_network) { ?>
<input type="hidden" name="editing_dni_network" value="1">
<input type="hidden" name="editing_dni_network_id" value="<?php echo $edit_dni_row['id'] ?? ''; ?>">
Expand All @@ -646,7 +655,7 @@ function lpo_ctx_pref_cache_bust($userId)
echo 'col-xs-7';
} ?>" id="dni_api_key_input_group" style="padding: 0px; padding-right: 5px;">
<label class="sr-only" for="dni_network_api_key">Add API key</label>
<input type="text" name="dni_network_api_key" class="form-control input-sm" placeholder="API Key" value="<?php echo $edit_dni_row['apiKey'] ?? ''; ?>">
<input type="text" name="dni_network_api_key" class="form-control input-sm" placeholder="API Key" value="<?php echo htmlspecialchars((string) ($edit_dni_row['apiKey'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>">
<div id="dniInfo"></div>
</div>
<div class="col-xs-2" id="dni_affiliate_id_input_group" style="<?php if (isset($editing_dni_network) && $editing_dni_network) {
Expand Down
10 changes: 9 additions & 1 deletion 202-account/auto-upgrade-premium.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

AUTH::require_user();

// Upgrading overwrites application files and runs DB migrations — the most
// powerful operation in the account area. Gate it like the Settings page
// instead of allowing any authenticated role to trigger it.
if (!$userObj->hasPermission("access_to_settings")) {
header('location: ' . get_absolute_url() . '202-account/');
exit;
}

// On managed deployments (Coolify, or any Docker image built from git) the
// 1-click upgrade would write into the ephemeral container filesystem and be
// silently reverted on the next redeploy — refuse before touching anything.
Expand Down Expand Up @@ -273,7 +281,7 @@

<?php if (($_POST['start_upgrade'] ?? '') === '1') { ?>
<br>
<textarea rows="8" class="form-control install_logs"><?php echo $installlog; ?></textarea>
<textarea rows="8" class="form-control install_logs"><?php echo htmlspecialchars((string) $installlog, ENT_QUOTES, 'UTF-8'); ?></textarea>
<?php }

if ($upgrade_done !== true) { ?>
Expand Down
8 changes: 8 additions & 0 deletions 202-account/auto-upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

AUTH::require_user();

// Upgrading overwrites application files and runs DB migrations — the most
// powerful operation in the account area. Gate it like the Settings page
// instead of allowing any authenticated role to trigger it.
if (!$userObj->hasPermission("access_to_settings")) {
header('location: ' . get_absolute_url() . '202-account/');
exit;
}

// On managed deployments (Coolify, or any Docker image built from git) the
// 1-click upgrade would write into the ephemeral container filesystem and be
// silently reverted on the next redeploy — refuse before touching anything.
Expand Down
4 changes: 2 additions & 2 deletions 202-account/clickservers.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
FROM `202_users`
WHERE `202_users`.`user_id`='".$mysql['user_id']."'";
$user_result = $db->query($user_sql);
$user_row = $user_result->fetch_assoc();
$user_row = $user_result ? ($user_result->fetch_assoc() ?: []) : [];
if ($user_row['clickserver_api_key']) {
$clickservers = clickserver_api_domain_list($user_row['clickserver_api_key']);
}
Expand Down Expand Up @@ -126,7 +126,7 @@
$(checkbox).bootstrapSwitch('toggleState');

} else {
if("<?php echo $_SERVER['HTTP_HOST'];?>" == clid){
if(<?php echo json_encode((string) ($_SERVER['HTTP_HOST'] ?? ''), JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP); ?> == clid){
window.location.href = "../202-account/signout.php";
}

Expand Down
Loading
Loading