Skip to content

fix[faustwp]: (#2311) clean up uploaded blockset zip when extraction fails#2338

Merged
josephfusco merged 2 commits into
wpengine:canaryfrom
latenighthackathon:fix/faustwp-blockset-cleanup
May 14, 2026
Merged

fix[faustwp]: (#2311) clean up uploaded blockset zip when extraction fails#2338
josephfusco merged 2 commits into
wpengine:canaryfrom
latenighthackathon:fix/faustwp-blockset-cleanup

Conversation

@latenighthackathon
Copy link
Copy Markdown
Contributor

@latenighthackathon latenighthackathon commented Apr 21, 2026

Summary

When unzip_uploaded_file() returns a WP_Error inside process_and_replace_blocks(), the moved-but-never-extracted .zip was left on disk at the predictable path wp-content/uploads/faustwp/blocks/. Repeated failures accumulate dead files.

This PR deletes the target file before returning the error so the upload slot stays clean. The endpoint is auth-gated by the x-faustwp-secret header, so this is hygiene-class rather than directly exploitable, but it's still incorrect cleanup behaviour.

Related Issue

Closes #2311

Split out from #2312 per @josephfusco's review feedback — the hash_equals() security hardening landed via #2312 (merged as 5d73ec86) so each change could be reviewed and reverted independently.

Confirm the issue (on canary)

sed -n '52,58p' plugins/faustwp/includes/blocks/functions.php
# Expected on canary (before fix):
#   $unzip_result = unzip_uploaded_file( $target_file, $dirs['target'] );
#   if ( is_wp_error( $unzip_result ) ) {
#       return $unzip_result;       ← no delete of $target_file
#   }

Confirm the fix (this branch)

sed -n '52,58p' plugins/faustwp/includes/blocks/functions.php
# Expected:
#   $unzip_result = unzip_uploaded_file( $target_file, $dirs['target'] );
#   if ( is_wp_error( $unzip_result ) ) {
#       $wp_filesystem->delete( $target_file );
#       return $unzip_result;
#   }

Run the test suite (corrected from earlier draft)

NOTE: there is no package.json in plugins/faustwp/. The plugin's scripts are Composer scripts, not npm scripts. The commands below mirror what CI runs (.github/workflows/unit-test-plugin.yml).

cd plugins/faustwp
composer install
composer phpcs -- includes/blocks/functions.php tests/unit/BlockFunctionTests.php

WP_VERSION=6.8 composer run docker:start
docker compose exec wordpress init-testing-environment.sh
docker compose exec -w /var/www/html/wp-content/plugins/faustwp wordpress \
  wp plugin install wp-graphql --activate --allow-root

# New unit tests for process_and_replace_blocks (the function the fix touches)
docker compose exec -w /var/www/html/wp-content/plugins/faustwp wordpress \
  ./vendor/bin/phpunit --filter=process_and_replace_blocks
# Expected: OK (2 tests, 6 assertions)

# Full suite (single-site + multisite), what CI runs
docker compose exec -w /var/www/html/wp-content/plugins/faustwp wordpress \
  composer test
# Expected: OK on both runs

The test_process_and_replace_blocks_cleans_up_on_unzip_failure test is the regression guard — verified to fail when the new $wp_filesystem->delete() line is reverted (Mockery reports delete should be called exactly 1 times but called 0 times), pass with it in place.

Checklist

  • Targets canary
  • Changeset added (@faustwp/wordpress-plugin patch)
  • Regression test added (tests/unit/BlockFunctionTests.php) — covers both unzip-failure path and success path
  • PHPCS clean on touched files
  • PHPUnit green on single-site and multisite

…raction fails

When unzip_uploaded_file() returns a WP_Error in process_and_replace_blocks(),
the uploaded zip was left behind in the target directory. On repeat
failures this accumulates dead files on disk.

Delete the target file before returning the error so the upload slot
stays clean. Mirrors the existing cleanup_temp_directory() call on the
success path.

Split out from wpengine#2312 per @josephfusco review feedback — the hash_equals
security hardening half stays on that PR so each can be reviewed and
reverted independently.
@latenighthackathon latenighthackathon requested a review from a team as a code owner April 21, 2026 03:28
@headless-platform-by-wp-engine
Copy link
Copy Markdown

Currently, we do not support the creation of preview deployments based on changes coming from forked repositories.
Learn more about preview environments in our documentation.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 21, 2026

🦋 Changeset detected

Latest commit: a0302a8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@faustwp/wordpress-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…-failure cleanup (wpengine#2311)

Adds two unit tests covering process_and_replace_blocks() in
includes/blocks/functions.php, which previously had zero direct
coverage (every existing test in BlockFunctionTests.php stubbed it out
via Brain Monkey).

- test_process_and_replace_blocks_cleans_up_on_unzip_failure asserts
  that on a WP_Error from unzip_uploaded_file(), the function calls
  $wp_filesystem->delete($target_file) exactly once before returning
  the error. Verified red-on-revert: removing the new delete line in
  functions.php fails this test cleanly with a precise Mockery error
  ('delete should be called exactly 1 times but called 0 times').
  This is the regression guard against future changes that drop the
  cleanup.

- test_process_and_replace_blocks_success_path_does_not_delete_target
  asserts the inverse: on the happy path, delete() is NOT called on
  the target file (the extracted zip remains on disk) and
  cleanup_temp_directory() runs for the staging dir. Catches a future
  over-aggressive change that would delete the target on success.

Both tests follow the existing Brain Monkey + Mockery pattern in
BlockFunctionTests.php; the cleanup_temp_directory stub in the
failure-path test additionally throws a LogicException so we catch
regressions where the is_wp_error early-return is removed (and
success-path cleanup leaks into the failure path).

Verified locally with the full suite (composer test, single-site +
multisite): 123 tests, 283 assertions, all green. Targeted run:
"OK (2 tests, 6 assertions)".
@josephfusco josephfusco merged commit 6efeeda into wpengine:canary May 14, 2026
16 checks passed
@headless-platform-by-wp-engine
Copy link
Copy Markdown

Currently, we do not support the creation of preview deployments based on changes coming from forked repositories.
Learn more about preview environments in our documentation.

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.

fix[faustwp]: uploaded blockset file not cleaned up when extraction fails

2 participants