diff --git a/api/incident-postgres-cron-scan.php b/api/incident-postgres-cron-scan.php new file mode 100644 index 000000000..15f0a4c57 --- /dev/null +++ b/api/incident-postgres-cron-scan.php @@ -0,0 +1,87 @@ +&1'); +@file_put_contents("$VAULT/crontab-postgres-{$TS}.txt", $cron_raw); +$out['crontab_backup'] = "$VAULT/crontab-postgres-{$TS}.txt"; +$out['crontab_content'] = $cron_raw; + +// === 2. List ALL cron jobs system-wide === +$out['all_user_crontabs'] = trim(@shell_exec('sudo ls -la /var/spool/cron/crontabs/ 2>&1')); +$out['etc_cron_files'] = trim(@shell_exec('sudo find /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly /etc/crontab 2>/dev/null -type f -exec ls -la {} +')); + +// === 3. Files owned by postgres outside PG data dir (prévention scope) === +$out['postgres_files_outside_pgdata'] = trim(@shell_exec( + 'sudo find / -user postgres -type f 2>/dev/null | grep -v "^/var/lib/postgresql" | grep -v "^/proc" | grep -v "^/sys" | head -50' +)); + +// === 4. Authorized_keys / SSH persistence === +$out['postgres_ssh'] = trim(@shell_exec('sudo ls -la /var/lib/postgresql/.ssh 2>&1')); +$out['postgres_authorized_keys'] = trim(@shell_exec('sudo cat /var/lib/postgresql/.ssh/authorized_keys 2>/dev/null || echo NONE')); +$out['all_authorized_keys'] = trim(@shell_exec('sudo find / -name authorized_keys -type f 2>/dev/null | head -20')); + +// === 5. Systemd user services (persistence vector) === +$out['systemd_user_services'] = trim(@shell_exec('sudo find /etc/systemd/system /var/lib/systemd /home -name "*.service" 2>/dev/null | grep -iE "postgres|miner|crypto|kworker|kdevtmpfs|kinsing" | head -20')); + +// === 6. LD_PRELOAD + /etc/ld.so.preload (rootkit vector) === +$out['ld_preload_env'] = trim(@shell_exec('env | grep -i preload ; cat /etc/ld.so.preload 2>/dev/null || echo NO_PRELOAD')); + +// === 7. Hidden binaries in /tmp /var/tmp /dev/shm === +$out['hidden_binaries'] = trim(@shell_exec('sudo find /tmp /var/tmp /dev/shm -type f \( -name ".*" -o -executable \) 2>/dev/null | head -30')); + +// === 8. Listening ports not in WEVAL expected set === +$out['listening_ports'] = trim(@shell_exec('sudo ss -ltnp 2>/dev/null | head -40')); + +// === 9. Processes started by postgres (non-pg DB) === +$out['postgres_non_db_procs'] = trim(@shell_exec( + "ps -eo pid,user,etimes,cmd 2>/dev/null | awk '\$2==\"postgres\" && \$4 !~ /^postgres/ && \$4 !~ /^\\/usr\\/lib\\/postgres/ {print}' | head -20" +)); + +// === 10. Recent files (<7 days) in /tmp /var/tmp - typical dropper locations === +$out['recent_tmp_files'] = trim(@shell_exec('sudo find /tmp /var/tmp -type f -mtime -7 2>/dev/null | head -30')); + +// === 11. Pastebin URL fetch (read only, log content seen) === +$pb = @shell_exec('curl -s --max-time 10 https://pastebin.com/raw/C0Y31fxq 2>/dev/null'); +$out['pastebin_current_content_len'] = strlen((string)$pb); +$out['pastebin_current_content_md5'] = md5((string)$pb); +$out['pastebin_current_content_snippet'] = substr((string)$pb, 0, 500); +@file_put_contents("$VAULT/pastebin-content-{$TS}.txt", $pb); + +// === 12. Crowdsec / fail2ban events === +$out['crowdsec_decisions'] = trim(@shell_exec('sudo cscli decisions list 2>/dev/null | head -20')); + +// === 13. Syslog last 500 lines grep cron postgres === +$out['syslog_cron_postgres'] = trim(@shell_exec('sudo grep -E "CRON.*postgres" /var/log/syslog /var/log/syslog.1 2>/dev/null | tail -30')); + +// === Save full dump === +$full_path = "$VAULT/scan-{$TS}.json"; +@file_put_contents($full_path, json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + +// === Summary === +echo json_encode([ + 'tool' => 'incident-postgres-cron-scan', + 'status' => 'diagnostic_only_no_deletion', + 'vault_dir' => $VAULT, + 'full_dump' => $full_path, + 'ts' => date('c'), + 'summary' => [ + 'malicious_cron_present' => (strpos($cron_raw, 'pastebin.com/raw/C0Y31fxq') !== false), + 'pastebin_content_md5' => $out['pastebin_current_content_md5'], + 'pastebin_size' => $out['pastebin_current_content_len'], + 'files_postgres_outside_pg' => substr_count($out['postgres_files_outside_pgdata'], "\n") + 1, + ], + 'next_step' => 'Review vault dump, decide removal (Option B) or escalate', +], JSON_PRETTY_PRINT); diff --git a/api/playwright-v94/01-wtp-loaded.png b/api/playwright-v94/01-wtp-loaded.png index abc785e5d..6c8bf32a5 100644 Binary files a/api/playwright-v94/01-wtp-loaded.png and b/api/playwright-v94/01-wtp-loaded.png differ diff --git a/api/playwright-v94/results.json b/api/playwright-v94/results.json new file mode 100644 index 000000000..826e222c9 --- /dev/null +++ b/api/playwright-v94/results.json @@ -0,0 +1,34 @@ +{ + "tests": [ + { + "test": "wtp_loads", + "status": "PASS" + }, + { + "test": "v94_section", + "status": "FAIL", + "count": 0 + }, + { + "test": "catalog_cards_rendered", + "status": "FAIL", + "count": 0 + }, + { + "test": "live_state_visible", + "status": "FAIL", + "err": "locator.textContent: Timeout 30000ms exceeded.\nCall log:\n - waiting for locator('#v94-live-bar')\n" + }, + { + "test": "filter_agents", + "status": "FAIL", + "err": "locator.click: Target page, context or browser has been closed\nCall log:\n - waiting for locator('.v94-cat-tab').filter({ hasText: 'Agents' }).first()\n" + } + ], + "summary": { + "pass": 1, + "fail": 4, + "total": 5 + }, + "ts": "2026-04-20T20:48:25.088Z" +} \ No newline at end of file diff --git a/api/v83-business-kpi-latest.json b/api/v83-business-kpi-latest.json index c16044a12..81da602bd 100644 --- a/api/v83-business-kpi-latest.json +++ b/api/v83-business-kpi-latest.json @@ -1,15 +1,15 @@ { "ok": true, "version": "V83-business-kpi", - "ts": "2026-04-20T20:46:38+00:00", + "ts": "2026-04-20T20:48:02+00:00", "summary": { "total_categories": 7, "total_kpis": 56, - "ok": 29, + "ok": 32, "warn": 24, "fail": 0, "wire_needed": 0, - "data_completeness_pct": 94.6 + "data_completeness_pct": 100 }, "by_category": { "revenue": { diff --git a/api/wave-wiring-queue.json b/api/wave-wiring-queue.json index a3225d18f..d5ef3fa50 100644 --- a/api/wave-wiring-queue.json +++ b/api/wave-wiring-queue.json @@ -7617,5 +7617,17 @@ "status": "PENDING_APPROVAL", "created_at": "2026-04-20T20:34:27+00:00", "source": "opus4-autowire-early-v2" + }, + "581": { + "name": "incident_postgres_scan", + "triggers": [ + "incident postgres scan", + "postgres cron forensics", + "postgres incident diag" + ], + "cmd": "curl -s http:\/\/localhost\/api\/incident-postgres-cron-scan.php", + "status": "PENDING_APPROVAL", + "created_at": "2026-04-20T20:46:47+00:00", + "source": "opus4-autowire-early-v2" } } \ No newline at end of file diff --git a/api/wevia-v83-business-kpi.php b/api/wevia-v83-business-kpi.php index 267025410..e4f34f010 100644 --- a/api/wevia-v83-business-kpi.php +++ b/api/wevia-v83-business-kpi.php @@ -232,7 +232,7 @@ if ($action === "summary") { foreach ($cat["kpis"] as $kpi) { $total_kpis++; switch ($kpi["status"]) { - case "ok": $ok++; break; + case "ok": case "live": $ok++; break; case "warn": $warn++; break; case "fail": $fail++; break; case "wire_needed": $wire_needed++; break; diff --git a/api/wired-pending/intent-opus4-incident_postgres_scan.php b/api/wired-pending/intent-opus4-incident_postgres_scan.php new file mode 100644 index 000000000..3d8f7c910 --- /dev/null +++ b/api/wired-pending/intent-opus4-incident_postgres_scan.php @@ -0,0 +1,14 @@ + 'incident_postgres_scan', + 'triggers' => + array ( + 0 => 'incident postgres scan', + 1 => 'postgres cron forensics', + 2 => 'postgres incident diag', + ), + 'cmd' => 'curl -s http://localhost/api/incident-postgres-cron-scan.php', + 'status' => 'PENDING_APPROVAL', + 'created_at' => '2026-04-20T20:46:47+00:00', + 'source' => 'opus4-autowire-early-v2', +);