Files
c-relay-pg/admin/api/cleanup_validate.php
T
Laan Tungir 50653dc86a v2.1.38 - Custom backfill feature: ad-hoc backfill jobs with arbitrary NIP-01 filters + admin UI isolation
- New caching_custom_backfill_jobs and caching_custom_backfill_batches tables
- Admin API (POST create/cancel, GET list) at admin/api/custom_backfill.php
- Admin UI form with preset buttons and job status table on Backfill page
- Caching daemon module (custom_backfill.c) processes jobs independently
- pg_inbox functions for job/batch claim, progress update, and cancel
- Three batch modes: author-batched, id-batched, time-window scan
- Fixed done_batches overcount on job completion
- Admin config now environment-variable driven (C_RELAY_DB_*)
- admin/serve.sh supports separate instances for different databases
- make_and_restart_relay.sh only kills relay on target port, not all relays
2026-08-07 14:49:13 -04:00

61 lines
2.1 KiB
PHP

<?php
/**
* admin/api/cleanup_validate.php — Live SQL validity check for the cleanup page.
*
* POST { "sql": "SELECT ..." }
* -> { "valid": true } | { "valid": false, "error": "..." }
*
* Uses EXPLAIN so the query is parsed + planned but never executed.
*/
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_response(['valid' => false, 'error' => 'POST only']);
exit;
}
$body = json_decode(file_get_contents('php://input'), true);
$sql = trim($body['sql'] ?? '');
if ($sql === '') {
json_response(['valid' => false, 'error' => 'No SQL provided']);
exit;
}
// The cleanup SQL box should only ever hold a SELECT (the preview query).
// Reject anything that isn't a SELECT so this can't be abused to run writes.
if (!preg_match('/^\s*SELECT\b/i', $sql)) {
json_response(['valid' => false, 'error' => 'Only SELECT queries are allowed in the SQL box']);
exit;
}
// Defense in depth: block statement chaining / write keywords.
$deny = '/\b(DELETE|UPDATE|INSERT|DROP|TRUNCATE|GRANT|REVOKE|ALTER|CREATE)\b/i';
if (preg_match($deny, $sql)) {
json_response(['valid' => false, 'error' => 'Disallowed keyword in query']);
exit;
}
if (strpos($sql, ';') !== false || strpos($sql, '--') !== false || strpos($sql, '/*') !== false) {
json_response(['valid' => false, 'error' => 'Statement separators / comments are not allowed']);
exit;
}
try {
// EXPLAIN parses + plans without executing. Wrap in a transaction that
// we roll back just to be extra safe (EXPLAIN itself doesn't write, but
// this guards against any function-side effects in expressions).
$pdo->beginTransaction();
try {
$pdo->query('EXPLAIN ' . $sql);
$pdo->rollBack();
json_response(['valid' => true]);
} catch (Throwable $e) {
if ($pdo->inTransaction()) $pdo->rollBack();
json_response(['valid' => false, 'error' => $e->getMessage()]);
}
} catch (Throwable $e) {
if (isset($pdo) && $pdo->inTransaction()) { try { $pdo->rollBack(); } catch (Throwable $x) {} }
json_response(['valid' => false, 'error' => $e->getMessage()]);
}