Initial bootstrap: cpanel-importer sanitization sandbox
Skeleton for the cpanel-importer Docker container — a one-shot sandbox the WHP panel invokes BEFORE extracting a customer cpmove tarball. See cpanel-import-container-spec.md (in /workspace/) for the full design. What this ships in v1.0: - Dockerfile: almalinux:10-minimal + PHP 8.4 (Remi) + ClamAV 1.4 + SaneSecurity Foxhole.PHP rules + tar/mariadb-client/rsync. Runs as UID 999 (whp-import) via the panel-side --user 999:999 flag. - scripts/entrypoint.sh: validates env, runs (optional) freshclam, drives extract -> scan-files -> scan-dbs -> rsync -> report.json. - scripts/extract.sh + scripts/lib/scan-symlinks.php: pre-extract symlink scan ported standalone from web-files/libs/CpanelBackupImporter.php (the existing 2026-05-29 whp02 destruction-vector fix). Aborts with exit 3 before tar runs if any DANGEROUS symlink is found. - scripts/scan-files.php: ClamAV walk + classify-and-action. v1.0 ships with an empty cleaner registry — every hit is QUARANTINE_ONLY. Cleaner hooks are stubbed for v1.1. - scripts/scan-dbs.php: regex MyISAM -> InnoDB rewrite (always applied), WordPress identification, and ONE WP content scan check (siteurl_external_domain). v1.1 will grow the check set. - scripts/lib/safety-net.php: container-narrow open_basedir allow-list, much tighter than the panel-side one. - .gitea/workflows/build-push.yaml: builds + smoke-tests + PHP-syntax-checks + bash-syntax-checks before pushing to repo.anhonesthost.net/cloud-hosting-platform/cpanel-importer. - tests/build-fixtures.sh: builds cpmove-clean.tar.gz (benign WP dump) and cpmove-alfa.tar.gz (the ALFA-shell symlink-to-/etc vector) for local end-to-end testing. - README.md / CONTRIBUTING.md: docker-run invocation, bind-mount catalog, report.json schema, how to add a cleaner pattern or a WP scan signature. Local acceptance test results: - clean fixture -> status=completed, 3 MyISAM->InnoDB, no flags, 0 - ALFA fixture -> exit 1, status=failed, failed_stage=extract, "tarball contains dangerous symlinks; aborting" on stderr - compromised-siteurl fixture -> imported_into_new_server=false, .flagged file written, summary_for_panel.show_alert=true Image size: 197 MB compressed (gzipped docker save), ~397 MB unique layers extracted. Well under the spec's 600 MB compressed / 1.2 GB extracted budget. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
scripts/lib/safety-net.php
Normal file
46
scripts/lib/safety-net.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* safety-net.php — container-narrow open_basedir allow-list.
|
||||
*
|
||||
* The sibling at /workspace/whp/web-files/includes/safety-net.php is the
|
||||
* panel's allow-list — it includes /docker, /root/whp, /etc/whp, etc.,
|
||||
* because the panel legitimately reads from those.
|
||||
*
|
||||
* Inside this container, the worker has a much smaller set of paths it
|
||||
* needs. Anything outside this list is blocked at the PHP filesystem-
|
||||
* function level (PHP enforces open_basedir in unlink/scandir/fopen/
|
||||
* RecursiveDirectoryIterator/etc. AFTER symlink resolution, so a planted
|
||||
* symlink-to-/proc cannot escape the allow-list).
|
||||
*
|
||||
* HISTORY — the same destruction-bug class that motivated the panel-side
|
||||
* safety-net (whp02 /usr/bin + /etc wipe, 2026-05-28/29) is the reason
|
||||
* this exists. In the container the host /etc /usr /root are not bind-
|
||||
* mounted, but open_basedir gives belt-and-suspenders enforcement
|
||||
* against any extracted-archive symlink walker we add later.
|
||||
*/
|
||||
|
||||
if (function_exists('ini_set')) {
|
||||
// Container-internal paths only. Notable absences:
|
||||
// - /etc, /usr, /var, /root — never written to by this container
|
||||
// - /docker — there is no /docker in this image
|
||||
// - /home — there is no /home in this image
|
||||
$allowed = implode(PATH_SEPARATOR, [
|
||||
'/host', // /host/backup (RO), /host/quarantine, /host/sanitized
|
||||
'/tmp', // tmpfs scratch space
|
||||
'/opt/whp', // WORKDIR + per-run state
|
||||
'/scripts', // our own code
|
||||
'/var/lib/clamav', // ClamAV signature DB
|
||||
'/var/log/clamav', // freshclam log
|
||||
'/etc/freshclam.conf', // single file, read-only
|
||||
'/proc/self', // pid/cgroup introspection
|
||||
]);
|
||||
|
||||
if ((string) ini_get('open_basedir') === '') {
|
||||
@ini_set('open_basedir', $allowed);
|
||||
}
|
||||
|
||||
// Realpath cache tuning matches the panel — open_basedir adds a
|
||||
// realpath() to every fs op, so a bigger cache pays back fast.
|
||||
@ini_set('realpath_cache_size', '512K');
|
||||
@ini_set('realpath_cache_ttl', '600');
|
||||
}
|
||||
Reference in New Issue
Block a user