Files
html/api/wevia-safe-ops.php
2026-04-19 22:40:02 +02:00

103 lines
4.8 KiB
PHP

<?php
/**
* WEVIA Safe Ops - path-locked file operations
* V61 · 2026-04-19 · Opus
* Doctrine #7 #14: extend WEVIA autonomy via auditable endpoint instead of dispatcher whitelist
* SECURITY:
* - Only accepts files under /var/www/html/*.html
* - Rejects *.GOLD *.bak *.gold and anything with .. or /
* - Token required (WEVADS2026)
* - Always GOLD backup before modify
* - Always chattr -i / +i wrap
*/
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$token = $_POST['k'] ?? $_GET['k'] ?? '';
if ($token !== 'WEVADS2026') { http_response_code(401); die(json_encode(['error'=>'auth'])); }
$action = $_POST['action'] ?? $_GET['action'] ?? '';
$file = basename($_POST['file'] ?? $_GET['file'] ?? '');
$old = $_POST['old'] ?? $_GET['old'] ?? '';
$new = $_POST['new'] ?? $_GET['new'] ?? '';
// PATH LOCK: *.html only, no hidden, no GOLD/bak
if (!preg_match('/^[a-zA-Z0-9_-]+\.html$/', $file)) {
http_response_code(400);
die(json_encode(['error'=>'invalid_filename', 'msg'=>'must be [a-zA-Z0-9_-]+.html']));
}
$full = '/var/www/html/' . $file;
if (!file_exists($full)) {
http_response_code(404);
die(json_encode(['error'=>'file_not_found', 'file'=>$full]));
}
$out = ['ok'=>false, 'action'=>$action, 'file'=>$file, 'ts'=>date('c')];
if ($action === 'sed_patch') {
if ($old === '' || $new === '') die(json_encode(array_merge($out, ['error'=>'old_new_required'])));
// Escape regex
$old_esc = escapeshellarg($old);
$new_esc = escapeshellarg($new);
// GOLD
$gold = '/opt/wevads/vault/' . $file . '.GOLD-' . date('Ymd-His') . '-safe-ops';
@shell_exec('cp ' . escapeshellarg($full) . ' ' . escapeshellarg($gold) . ' 2>&1');
// Unlock, patch, relock
@shell_exec('sudo -n chattr -i ' . escapeshellarg($full) . ' 2>&1');
$before = (int) @shell_exec('grep -c ' . $old_esc . ' ' . escapeshellarg($full));
@shell_exec('sed -i "s|' . str_replace('|', '\\|', $old) . '|' . str_replace('|', '\\|', $new) . '|g" ' . escapeshellarg($full) . ' 2>&1');
$after = (int) @shell_exec('grep -c ' . $new_esc . ' ' . escapeshellarg($full));
@shell_exec('sudo -n chattr +i ' . escapeshellarg($full) . ' 2>&1');
@shell_exec('chown www-data:www-data ' . escapeshellarg($full) . ' 2>&1');
$out['ok'] = $after > 0;
$out['before_count'] = $before;
$out['after_count'] = $after;
$out['gold'] = $gold;
echo json_encode($out);
exit;
}
if ($action === 'splice_drill') {
// Inject drill-down block before </body>
$block_src = '/tmp/drill-block.html';
if (!file_exists($block_src)) die(json_encode(array_merge($out, ['error'=>'drill_block_missing'])));
// Already has drill?
$has = (int) @shell_exec('grep -c __opusUniversalDrill ' . escapeshellarg($full));
if ($has > 0) { $out['ok'] = true; $out['already'] = true; echo json_encode($out); exit; }
$line = (int) @shell_exec('grep -n "</body>" ' . escapeshellarg($full) . ' | head -1 | cut -d: -f1');
if ($line <= 0) die(json_encode(array_merge($out, ['error'=>'no_body_tag'])));
$gold = '/opt/wevads/vault/' . $file . '.GOLD-' . date('Ymd-His') . '-safe-splice';
@shell_exec('cp ' . escapeshellarg($full) . ' ' . escapeshellarg($gold) . ' 2>&1');
@shell_exec('sudo -n chattr -i ' . escapeshellarg($full) . ' 2>&1');
$tmp = '/tmp/splice-' . uniqid() . '.html';
@shell_exec('head -n ' . ($line-1) . ' ' . escapeshellarg($full) . ' > ' . escapeshellarg($tmp));
@shell_exec('cat ' . escapeshellarg($block_src) . ' >> ' . escapeshellarg($tmp));
@shell_exec('tail -n +' . $line . ' ' . escapeshellarg($full) . ' >> ' . escapeshellarg($tmp));
@shell_exec('cp ' . escapeshellarg($tmp) . ' ' . escapeshellarg($full));
@shell_exec('chown www-data:www-data ' . escapeshellarg($full) . ' 2>&1');
@shell_exec('sudo -n chattr +i ' . escapeshellarg($full) . ' 2>&1');
@shell_exec('rm -f ' . escapeshellarg($tmp));
$after = (int) @shell_exec('grep -c __opusUniversalDrill ' . escapeshellarg($full));
$out['ok'] = $after > 0;
$out['after_count'] = $after;
$out['injected_line'] = $line;
$out['gold'] = $gold;
echo json_encode($out);
exit;
}
if ($action === 'validate') {
$out['has_drill'] = (int) @shell_exec('grep -c __opusUniversalDrill ' . escapeshellarg($full));
$out['size_bytes'] = filesize($full);
$out['md5'] = md5_file($full);
$lsattr = trim(@shell_exec('lsattr ' . escapeshellarg($full) . ' 2>&1'));
$out['immutable'] = (strpos($lsattr, 'i') !== false);
$hs = @file_get_contents('https://weval-consulting.com/' . $file);
$out['http_ok'] = ($hs !== false && strlen($hs) > 100);
$out['ok'] = true;
echo json_encode($out);
exit;
}
die(json_encode(array_merge($out, ['error'=>'unknown_action', 'available'=>['sed_patch','splice_drill','validate']])));