sanitize-dont-refuse: strip dangerous symlinks via tar --exclude
All checks were successful
cpanel-importer Build and Push / Build-and-Push (push) Successful in 1m10s
All checks were successful
cpanel-importer Build and Push / Build-and-Push (push) Successful in 1m10s
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>
This commit is contained in:
@@ -159,15 +159,18 @@ rsync -a --no-owner --no-group --no-perms --chmod=Du=rwx,Dg=rx,Do=,Fu=rw,Fg=r,Fo
|
||||
STAGE="write_report"
|
||||
log "stage: write_report"
|
||||
DURATION=$(( $(date -u +%s) - START_TS ))
|
||||
STRIPPED_SYMLINKS_FILE="$EXTRACT_DIR/.cpanel-importer-stripped-symlinks.json"
|
||||
php -r '
|
||||
$importId = $argv[1];
|
||||
$duration = (int) $argv[2];
|
||||
$filesPath = $argv[3];
|
||||
$dbsPath = $argv[4];
|
||||
$outPath = $argv[5];
|
||||
$importId = $argv[1];
|
||||
$duration = (int) $argv[2];
|
||||
$filesPath = $argv[3];
|
||||
$dbsPath = $argv[4];
|
||||
$strippedPath = $argv[5];
|
||||
$outPath = $argv[6];
|
||||
|
||||
$files = is_file($filesPath) ? json_decode(file_get_contents($filesPath), true) : null;
|
||||
$dbs = is_file($dbsPath) ? json_decode(file_get_contents($dbsPath), true) : null;
|
||||
$files = is_file($filesPath) ? json_decode(file_get_contents($filesPath), true) : null;
|
||||
$dbs = is_file($dbsPath) ? json_decode(file_get_contents($dbsPath), true) : null;
|
||||
$stripped = is_file($strippedPath) ? json_decode(file_get_contents($strippedPath), true) : null;
|
||||
|
||||
$filesScanned = $files["files_scanned"] ?? 0;
|
||||
$filesClean = $files["files_clean"] ?? 0;
|
||||
@@ -176,6 +179,18 @@ $filesQuarantined = $files["files_quarantined"] ?? 0;
|
||||
$actions = $files["actions"] ?? [];
|
||||
$databases = $dbs["databases"] ?? [];
|
||||
|
||||
// Prepend the stripped-symlinks actions from extract.sh so the operator
|
||||
// sees them at the top of the actions[] table on the results page. Bumps
|
||||
// files_quarantined because the strip-action is morally equivalent to a
|
||||
// quarantine - the entry was not extracted, the symlink file is "in the
|
||||
// archive but absent from the cleaned tree".
|
||||
$strippedActions = $stripped["actions"] ?? [];
|
||||
$strippedCount = count($strippedActions);
|
||||
if ($strippedCount > 0) {
|
||||
$actions = array_merge($strippedActions, $actions);
|
||||
$filesQuarantined += $strippedCount;
|
||||
}
|
||||
|
||||
$dbRefused = 0;
|
||||
foreach ($databases as $db) {
|
||||
if (($db["imported_into_new_server"] ?? true) === false) $dbRefused++;
|
||||
@@ -184,13 +199,24 @@ foreach ($databases as $db) {
|
||||
$severity = "info";
|
||||
$alert = false;
|
||||
$msg = "Sanitization clean: no malware signatures detected.";
|
||||
if ($filesQuarantined > 0 || $dbRefused > 0) {
|
||||
if ($filesQuarantined > 0 || $dbRefused > 0 || $strippedCount > 0) {
|
||||
$alert = true;
|
||||
$severity = ($filesQuarantined > 50 || $dbRefused > 0) ? "warning" : "info";
|
||||
$msg = sprintf(
|
||||
"%d files quarantined + %d cleaned in place; %d database(s) refused as compromised. Customer site may have been compromised at the source — recommend review.",
|
||||
$filesQuarantined, $filesCleaned, $dbRefused
|
||||
);
|
||||
$severity = ($filesQuarantined > 50 || $dbRefused > 0 || $strippedCount > 0) ? "warning" : "info";
|
||||
$parts = [];
|
||||
if ($strippedCount > 0) {
|
||||
$parts[] = sprintf("%d dangerous symlink(s) stripped during extract", $strippedCount);
|
||||
}
|
||||
if ($filesQuarantined - $strippedCount > 0) {
|
||||
$parts[] = sprintf("%d files quarantined", $filesQuarantined - $strippedCount);
|
||||
}
|
||||
if ($filesCleaned > 0) {
|
||||
$parts[] = sprintf("%d cleaned in place", $filesCleaned);
|
||||
}
|
||||
if ($dbRefused > 0) {
|
||||
$parts[] = sprintf("%d database(s) refused as compromised", $dbRefused);
|
||||
}
|
||||
$msg = implode("; ", $parts)
|
||||
. ". Customer site may have been compromised at the source — recommend review.";
|
||||
}
|
||||
|
||||
$report = [
|
||||
@@ -212,7 +238,7 @@ $report = [
|
||||
|
||||
file_put_contents($outPath, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
|
||||
fprintf(STDERR, "report written: %s\n", $outPath);
|
||||
' "$IMPORT_ID" "$DURATION" /tmp/scan-files-report.json /tmp/scan-dbs-report.json "$SANITIZED_DIR/report.json" \
|
||||
' "$IMPORT_ID" "$DURATION" /tmp/scan-files-report.json /tmp/scan-dbs-report.json "$STRIPPED_SYMLINKS_FILE" "$SANITIZED_DIR/report.json" \
|
||||
|| die "report merge failed"
|
||||
|
||||
log "done — exited cleanly after ${DURATION}s"
|
||||
|
||||
Reference in New Issue
Block a user