From 4888f85b5483da1ddbbefffb211556fe79fbc676 Mon Sep 17 00:00:00 2001 From: "Claude (bootstrap)" Date: Sun, 31 May 2026 12:08:40 -0700 Subject: [PATCH] scan-files: silence open_basedir warnings during walk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Container run on darkside completed end-to-end successfully (170,753 files scanned, 78 symlinks skipped via filter, 65 quarantined, walk errors = 0) — but the import log was flooded with PHP Warnings on every cpmove-internal symlink whose absolute target points outside the container's open_basedir allow-list: PHP Warning: is_link(): open_basedir restriction in effect. File(/host/sanitized/.../cybercoveconsulting.com/wp-content/db.php) is not within the allowed path(s): (/host:/tmp:...) The actual code path was correct — is_link() still returns true when warning, so the filter callback properly skipped these. But the noise made the streamed [container] log on the panel side unreadable (hundreds of warning lines per real signal line). Root cause: PHP's open_basedir check normalizes via realpath() even for is_link/is_file. cpmove symlinks like: db.php -> /home//.com/wp-content/db.php access-logs -> /usr/local/apache/domlogs/ cpanel-styled -> /usr/local/cpanel/base/frontend/.../glass have absolute targets that don't exist anywhere in the container (no /home, no /usr/local), so realpath() can't normalize them under any allow-list entry. PHP fires Warning, returns the lstat answer anyway, and our filter handles the skip correctly. Fix: a scoped set_error_handler around the walk that suppresses ONLY E_WARNINGs containing 'open_basedir restriction'. Non-open_basedir warnings still surface. The handler is restored immediately after the file-count loop, so subsequent stages (clamscan output parsing, quarantine actions) keep the default error reporting. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/scan-files.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/scripts/scan-files.php b/scripts/scan-files.php index 6bd939c..39de3ac 100755 --- a/scripts/scan-files.php +++ b/scripts/scan-files.php @@ -128,6 +128,30 @@ $filesScanned = 0; $skippedLinks = 0; $walkErrors = 0; +// Silence open_basedir E_WARNINGs during the walk. cPanel cpmove tarballs +// frequently contain symlinks pointing at the SOURCE server's absolute +// paths (db.php -> /home///wp-content/db.php, access-logs -> +// /usr/local/apache/domlogs/, etc.). PHP's open_basedir check +// normalizes via realpath() even for is_link/is_file; when the symlink +// target lies outside the container's allow-list (it does — there's no +// /home or /usr/local in the container), PHP emits a Warning per call. +// Our filter callback STILL returns the right answer (is_link returns +// true even when warning), so the skip-symlink logic works regardless +// of the noise — but the noise floods 100k+ lines into the import log +// for a typical customer account, drowning the actually-useful +// quarantine actions. +// +// set_error_handler limited to E_WARNING with a needle filter is +// narrower than `error_reporting(0)` (we still surface non-open_basedir +// warnings) and narrower than `@`-prefixing every call site (covers +// PHP-internal callbacks invoked deep inside the iterator). +$prevHandler = set_error_handler(function ($errno, $errstr) { + if ($errno === E_WARNING && strpos($errstr, 'open_basedir restriction') !== false) { + return true; // suppress + } + return false; // let PHP handle (or chain to previous handler) +}, E_WARNING); + $rdi = new RecursiveDirectoryIterator( $extractDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_PATHNAME @@ -154,6 +178,8 @@ foreach ($it as $pathname) { $walkErrors++; } } + +restore_error_handler(); fwrite(STDERR, sprintf( "scan-files: file walk: counted=%d, symlinks-skipped=%d, walk-errors=%d\n", $filesScanned, $skippedLinks, $walkErrors