security: fix 3 critical vulnerabilities

1. Remove raw API key from get_settings response
   - getServerSettings() no longer returns gemini_key in plain text
   - Only gemini_key_set (boolean) and settings_token_set (boolean)
   - JS updated to only check gemini_key_set (removes stale gemini_key fallback)

2. Protect save_settings with SETTINGS_TOKEN
   - If SETTINGS_TOKEN is set in .env, all save_settings calls must
     include matching X-Settings-Token header (uses hash_equals)
   - Empty token = no protection (backwards-compatible default)
   - Settings UI (Security tab) has a token input field
   - Wrong/missing token returns HTTP 403 with error 'unauthorized'
   - JS shows '🔒 Token non valido o mancante' on 403

3. DEMO_MODE native blocking in PHP
   - DEMO_MODE=false added to .env (default off)
   - When DEMO_MODE=true, all write actions return HTTP 403 before routing
   - Blocked: save_settings, product_save/delete/merge, inventory_add/use/update/remove,
     dismiss_anomaly, bring_add/remove/sync
   - demo_mode flag exposed via get_settings so JS can adapt UI
This commit is contained in:
dadaloop82
2026-05-04 06:20:23 +00:00
parent 529c09fda3
commit bf27469228
3 changed files with 59 additions and 9 deletions
+27 -1
View File
@@ -191,6 +191,20 @@ $action = $_GET['action'] ?? '';
if (!defined('CRON_MODE')):
try {
// DEMO_MODE guard
if (env('DEMO_MODE') === 'true') {
$demoBlocked = [
'save_settings', 'product_save', 'product_delete', 'product_merge',
'inventory_add', 'inventory_use', 'inventory_update', 'inventory_remove',
'dismiss_anomaly', 'bring_add', 'bring_remove', 'bring_sync',
];
if (in_array($action, $demoBlocked, true)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'demo_mode']);
exit;
}
}
switch ($action) {
// ===== PRODUCTS =====
case 'search_barcode':
@@ -2037,9 +2051,10 @@ function getServerSettings(): void {
$bringEmail = env('BRING_EMAIL');
echo json_encode([
'gemini_key' => $geminiKey,
'gemini_key_set' => !empty($geminiKey),
'bring_email' => $bringEmail,
'settings_token_set' => !empty(env('SETTINGS_TOKEN')),
'demo_mode' => env('DEMO_MODE') === 'true',
'bring_password_set' => !empty(env('BRING_PASSWORD')),
'tts_url' => env('TTS_URL'),
'tts_token' => env('TTS_TOKEN'),
@@ -2066,6 +2081,17 @@ function getServerSettings(): void {
}
function saveSettings(): void {
// Require SETTINGS_TOKEN if configured
$requiredToken = env('SETTINGS_TOKEN');
if (!empty($requiredToken)) {
$provided = $_SERVER['HTTP_X_SETTINGS_TOKEN'] ?? '';
if (!hash_equals($requiredToken, $provided)) {
http_response_code(403);
echo json_encode(['success' => false, 'error' => 'unauthorized']);
return;
}
}
$input = json_decode(file_get_contents('php://input'), true);
$envFile = __DIR__ . '/../.env';
$envVars = loadEnv();