rc=137 OOM kill triaged on whp02 darkside import. dmesg confirmed:
memory: usage 2097100kB, limit 2097152kB, failcnt 132
oom_kill_process ... task=bash uid=999
Root cause: extract.sh untars the cpmove into EXTRACT_DIR which was
/tmp/extract — a tmpfs mount (RAM-backed). The container's
--memory 2g cgroup ceiling counts tmpfs writes against RSS, so the
3 GB cpmove decompressing into tmpfs hit the limit at ~7s into tar
and the kernel killed the bash process running extract.sh.
Fix is structural, not a memory bump: the disk-backed bind mount
at /host/sanitized (mapped to /var/lib/whp/cpanel-importer-extract
on host) has effectively unlimited capacity and doesn't count against
the cgroup memory limit. Moving the working dirs there sidesteps the
OOM class entirely.
Layout change:
EXTRACT_DIR /tmp/extract -> $SANITIZED_DIR/extract-work
WORK_DIR /tmp/sanitized -> $SANITIZED_DIR/work
Two ripple changes:
- The old rsync_out stage cross-filesystem-copied ~10 GB from tmpfs
to /host/sanitized/<id>/extracted. That's now a same-filesystem
`mv` (constant-time rename) since extract-work IS already inside
/host/sanitized/<id>/. Stage renamed to finalize_layout for
clarity; pre-existing wipe of extracted/ + mysql/ guards against
partial-run residue.
- The stripped-symlinks actions sidecar moved to /tmp explicitly
(entrypoint.sh passes the 4th arg to extract.sh) so finalize's
rename doesn't (a) carry a dotfile into the cleaned tree the
panel imports and (b) move it out from under write_report's read.
Also fixes the unrelated-but-cosmetic freshclam warning by cd'ing to
/var/lib/clamav (the configured DatabaseDirectory, tmpfs writable)
before invoking freshclam in a subshell. The "Can't create
freshclam.dat in /opt/whp" errors were because /opt/whp is the
container WORKDIR which lives on the read-only rootfs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shifts the sandbox's symlink handling from "refuse the whole tarball"
to "drop the dangerous entries from extraction and record them as
quarantine actions". This is what sandbox mode is supposed to do —
make malicious cpmoves safe to import rather than gate-keeping them.
Three coordinated changes:
1. scan-symlinks.php — exit 0 even when DANGEROUS findings exist. The
JSON report is the source of truth; the caller decides what to do
with it. Usage/IO errors still exit 2. STDERR still names each
finding (now "STRIP X -> Y" instead of "refusing tarball") so the
streamed [container] log on the panel side surfaces them.
2. extract.sh — reads the scan-symlinks report, builds a
newline-delimited exclude list of DANGEROUS archive_paths, and
passes it to `tar --exclude-from=`. The stripped entries never
reach the filesystem; tar skips them silently. Also writes a small
JSON sidecar at $EXTRACT_DIR/.cpanel-importer-stripped-symlinks.json
describing each strip-action so the merge step can surface them in
report.json without re-parsing scan-symlinks output.
3. entrypoint.sh write_report — reads the sidecar, prepends each
stripped_dangerous_symlink action to the actions[] list, bumps
files_quarantined by the strip-count, and rewrites
summary_for_panel.alert_message to call them out distinctly:
"N dangerous symlink(s) stripped during extract; M files
quarantined; K cleaned in place. Customer site may have been
compromised at the source — recommend review."
Result on darkside: instead of the import failing on the ALFA
alfasymlink/root entry, that entry is silently skipped during
extract, recorded as `stripped_dangerous_symlink path=... target=/
reason=absolute target is root /`, and the rest of the tarball
extracts normally. Subsequent ClamAV scan + DB sanitization run
to completion; panel sees a verdict-completed import with the
stripped symlinks visible in the Sanitization Sandbox panel on the
results page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>