Skip to content

Release v2.1.1

Choose a tag to compare

@github-actions github-actions released this 08 Oct 03:23

Security

  • Security Review Follow-up: Refresh executable metadata before identity checks, verify root-owned executables by native numeric UID, retain PHP 8.2 child exit status across process-group liveness polls, limit each process diagnostic read to 32 KiB, reject non-removable download output buffers before attempting to discard them, and stop the owned WP-CLI/database-client POSIX process group on failure or timeout.
  • Multisite Export Authorization: Full-site export access now uses a shared permission helper. On multisite, exporter page access, export creation, secure download, and manual delete require a super admin or manage_network_options; single-site installs continue to require manage_options.
  • Per-Export Private Storage: Each export is now staged in a random private child directory under the configured private temp export base. The exporter rejects symlinked or unsafe pre-existing directories and enforces 0700 directory permissions.
  • Private File Modes: Sensitive export artifacts now use a private umask during export and WordPress Filesystem-backed 0600 chmod verification after database dumps, compressed database payloads, file archives, manifests, final ZIPs, and protection files are created.
  • WP-CLI Trust Boundary: WP-CLI discovery now prefers trusted system paths (/usr/local/bin/wp, /usr/bin/wp). Alternate executables must be explicitly configured with SSE_WP_CLI_PATH or the sse_wp_cli_path filter and must pass ownership and writable-mode checks.
  • Export Action Binding: Generated download and delete actions include the private export directory identifier in their request data and nonce action so requests are tied to the generated export location.
  • Shell-Free WP-CLI Execution: Database exports now run WP-CLI through a shell-free argument array with executable parent, ownership, mode, device, and inode verification; bounded diagnostics; a remaining-budget-derived timeout; live output monitoring; and bounded graceful/forced termination before process close.
  • Private-Directory-Only Actions: Download, deletion, and cleanup paths now require the generated private directory identifier. Obsolete direct-base ZIP discovery has been removed, and downloads open and identity-check the exact file handle before clearing all output buffers and sending attachment headers.
  • Renewable Export Lease: Replaced the transient check/set lock with an atomic, owner-bound current-site or current-network option lease. Byte-exact renewal and release prevent database collation rules or stale workers from mutating replacement owners, and owner-bound scheduled recovery removes interrupted private staging directories after expiry.
  • Export Resource Limits: Added filterable aggregate limits for source entries, source bytes, generated bytes, elapsed time, and minimum free disk space, with live checks during database, gzip, files archive, and final ZIP growth.
  • Log Data Lifecycle: Security and error records now reject malformed stored data, redact local paths, expire after seven days through independent recurring housekeeping, and remain capped at 20 entries.
  • Long File Names: A file whose path is longer than 100 bytes no longer stops the export. Any user who can upload media could create such a file. Long paths are now stored with GNU long-name records.
  • Lease Recovery: The database lock that guards lease creation is now taken on single sites as well as networks. A stored lease that fails validation, or a duplicate lease row on a network, is removed at the next export attempt instead of blocking every export; a valid, unexpired lease is never removed. A backward clock step can no longer store an unusable lease.
  • Stricter Directory Name Check: The check of a private export directory name no longer accepts a name that ends in a line break. The pattern now ends at the true end of the name.
  • Stored Value Hardening: The lease row is decoded with classes disallowed, and the scheduled deletion and recovery handlers check the type of their stored argument instead of failing on an unexpected one.
  • Activity Records: Each export, download, and deletion is now recorded with its time and user, whether or not debug logging is on, and error and security events are stored the same way. The client IP address is no longer stored, and existing records lose it at the next write.
  • Exports Removed on Deactivation and Deletion: Deactivating the plugin now deletes every export archive of the installation, clears all four scheduled events, and removes the export lease, because an inactive plugin can no longer delete an archive. Deleting the plugin also removes its stored records and its export directory. An export that is running when the plugin is deactivated stops and leaves nothing behind.
  • Installation-Specific Export Directory: The export directory name now ends in a value derived from the installation's secret salts and its path, so another local user cannot predict or pre-create it and two installations never share one. Archives left in the earlier fixed-name directory are still cleaned up by the same age rules, and that directory is removed once it is empty.
  • Wider Web-Root Check: An export is refused when the temporary directory resolves inside the WordPress directory, the content directory, the uploads directory, or the document root, including through a symbolic link. The message says to define WP_TEMP_DIR as a private directory outside the web root.
  • Link-Safe Directory Removal: Removing an export directory no longer follows a symbolic link placed inside it; the link is removed and its target is left alone.
  • WP-CLI Process Context: The database export now starts WP-CLI in the filesystem root, with packages skipped and with WP-CLI's global configuration pointed at a file that does not exist, so a wp-cli.yml in the site tree or a configuration file in the home directory is not read. The child still receives the server process's own environment, and never values from the request.
  • Log Content: Debug-log lines and stored records no longer contain the random name of a private export directory, and control characters are replaced, so a file name with a line break cannot forge a log line.
  • Unencrypted Page Warning: The exporter page warns when it is not served over HTTPS, because a downloaded export contains the whole database and wp-config.php.
  • Request Input Handling: The download and delete handlers no longer rewrite the requested file name before validating it; the anchored name patterns and the resolved-path check decide.

Architecture

  • Cleanup Entry Types: Use WordPress directory-list entry fields directly instead of redundant type checks and array-key casts, retaining failed-read handling and cleanup safety checks.
  • Canonical Admin Routing: Kept the exporter beneath Tools on single-site installations and moved the full-network exporter beneath Settings in Network Admin on multisite. Redirects now use the matching canonical admin URL, and assets bind to the exact page hook returned by WordPress in either context.
  • Private Export Cleanup: Bulk cleanup now scans generated private export directories with the WordPress Filesystem API, excludes the active lease directory, and removes interrupted or failed private staging directories through immediate failure cleanup, owner-bound recovery, and containment-checked deletion.
  • WordPress API Coverage: Replaced direct file metadata checks, generated artifact verification, export cleanup directory scans, and filename basename extraction with WordPress Filesystem API methods and native WordPress helpers where available.
  • Private Storage Helpers: Added shared helpers for multisite-aware capability checks, private export directory naming, generated directory validation, generated file verification, and private filesystem mode enforcement.
  • Direct Filesystem Contract: Export operations now fail closed unless WordPress provides its direct local filesystem transport, matching the native path and archive operations used by the exporter.
  • Typed WordPress Boundaries: Filesystem callers now consume the verified direct transport instead of rereading WordPress' mixed global, database access is narrowed once and uses the native %i identifier placeholder, and option, transient, filter, iterator, and request-local resource values are normalized at their boundaries.
  • Early Export Prerequisites: The 64-bit, ZipArchive, gzip, and disk_free_space() requirements now run before directory creation, WP-CLI discovery, resource-limit evaluation, or database export.
  • PHP 8.2 Baseline: Added native union return and parameter types, adopted PHP 8 string helpers and catch-without-variable syntax, and added an explicit 64-bit runtime requirement for archive size safety.
  • Analyzer Policy: Removed broad Psalm file/directory suppressions, replaced them with narrow source-boundary annotations, and completed the typed-boundary cleanup so both Psalm level 1 and PHPStan max report no errors without new suppressions or weaker policy.
  • Cron Diagnostics: Cleanup scheduling now requests WP_Error results and records actionable error codes and messages.
  • VIPCS Tooling: Added VIP Coding Standards as a Composer-managed dev dependency so the project PHPCS script can run the VIP ruleset reproducibly.
  • Semver Test Prep: Added a Composer-managed semantic versioning library for future version-related tests.
  • Streaming Files Archive: The files archive is now written by the plugin in one pass, straight into a gzip stream, instead of through PharData. Each file is read once, so the time grows with the size of the site and no longer with the square of the number of files, and no uncompressed copy is staged on disk. The PHP Phar extension is no longer required. Limits on time, free space, and generated size are checked while each file is written.
  • Files Archive Contents: A directory that cannot be opened is skipped instead of stopping the export. The contents of .git, .svn, and .hg directories are left out. A file named 0 is no longer skipped. Only regular files are archived, and a file that changes while it is read cannot corrupt the entries after it. The free-space reserve is measured on the volume that holds the archive. Skipped entries are counted and logged.
  • Request-Carried Size Limit: The per-file size limit now travels with the export request instead of a per-user transient that a second submission could remove. Only the sizes offered on the form are accepted; other limits remain available through the sse_max_file_size_for_export filter.
  • Archive Names: The site identifier in archive names is now the site's host unchanged, for example www.example.com instead of www.example_.com. International names are converted to their ASCII form, and every generated name passes the plugin's own name check.
  • Manifest Keys: manifest.txt gains home_url, site_url, table_prefix, and wp_config_in_archive lines after the existing ones, which are unchanged.
  • Import Requirement Warning: The exporter page warns when wp-config.php is outside the WordPress directory, or when neither WP_HOME nor WP_SITEURL is defined, because the EngineScript importer needs both. The export is still created.
  • WP-CLI Diagnostics: A WP-CLI executable that exists but fails a safety check is now reported with that reason instead of "not found".
  • Database Step Reliability: The check that a generated file has content now reads fresh file metadata. Before, an export of a site with a small database could fail about one time in twenty with "WP-CLI could not create the database export" although the dump had been written.
  • Available Exports List: The exporter page now lists every finished archive with its size and creation time, and with download and delete controls, for as long as the archive exists. The success notice no longer carries the only copy of those controls. Every user who may export sees every archive.
  • Recent Activity: The exporter page shows the stored records of exports, downloads, deletions, errors, and security events.
  • Export Progress: Submitting the export form disables the button and shows a status line, and a second submission is not sent. The form works as before without JavaScript.
  • Network-Only Activation: The plugin header now declares Network: true, so a multisite network offers network activation only. The exporter page was already available only in Network Admin.
  • Download Rate Limit: The one-download-per-minute limit now starts when the file has been opened, so a request that fails earlier does not block a retry.
  • Expired Exports: A download or delete request for an archive that no longer exists now gets a plain "expired or was deleted" message and no security record.
  • Housekeeping on Networks: The daily housekeeping event is scheduled on a network's main site only; other sites drop an event that an earlier version scheduled there.
  • Dead Code Removal: Removed an unneeded require_once, unused array keys and parameters, a Windows path form in POSIX-only code, a call to a function WordPress does not define, and other unreachable branches. No behavior changes.
  • Release Package: The release ZIP no longer contains README.md or CHANGELOG.md, or any other Markdown file. readme.txt carries the description and the changelog. The package check now applies the file rules of the WordPress.org plugin directory: it refuses a hidden file or directory, a compressed archive, a .phar, an executable or script, a name with a space or special character, two names that differ only by case, and a Markdown file.
  • Stricter Analysis: PHPStan now runs at level max and Psalm at error level 2. Exclusions and overrides in phpcs.xml, phpmd.xml, and phpstan.neon that had no effect were removed, so the rules they named apply again. Added a yamllint configuration and a .distignore; the package check fails when .distignore and the release file list disagree.
  • Component Files: The export lease functions and the WP-CLI process functions moved, unchanged, from includes/export.php into includes/lease.php and includes/process.php. Inline analyzer suppressions that no longer silenced any rule were removed, and the rest name only the rules that apply.
  • Bounded Request Timer: Align PHP's request execution timer with the filterable export-time policy, capped at 30 minutes, so ordinary 30-second defaults do not interrupt valid archives outside the plugin's owner-bound recovery and resource controls.
  • Scheduled Deletion After a Manual Delete: Deleting an export from the exporter page now also removes its scheduled deletion event, so the event no longer runs five minutes later for a file that is already gone.

Accessibility

  • Exporter Page Semantics: The settings table is marked as presentational, the size description is tied to its control, the link that opens a new tab says so, each archive's download and delete controls name their file, and the running-export status is announced to assistive technology.
  • Admin Warning Contrast: Updated warning text to meet WCAG AA normal-text contrast on the default WordPress admin surfaces and added forced-colors support while preserving native keyboard focus and responsive action wrapping.

Documentation

  • Plugin Check Policy: Document call-specific exceptions for supervised process creation, process-pipe cleanup, and the bounded request timer without weakening runtime safeguards. Update Tested up to to WordPress 7.1 based on the recorded compatibility matrix.
  • Text and Localization: Clarified scheduled cleanup, file exclusions, size limits, runtime requirements, and debug-log privacy; corrected user messages and developer comments; regenerated the translation catalog.
  • Text Review: Error messages are now complete sentences with one wording for failures ("Could not"), and user messages no longer use the internal word "lease". The exporter page and the documentation state plainly that an export contains the whole site, including password hashes and wp-config.php, and who can create one. Documented what the EngineScript importer requires, the installation-specific export directory, that there is no fallback to the uploads directory, that exports are deleted on deactivation, and two known limits (database read replicas, and the dump path in the process list). Corrected the description of the WP-CLI ownership rule. Regenerated the translation catalog.
  • Security and Navigation Requirements: Updated README.md and readme.txt to describe trusted WP-CLI and POSIX process-supervision requirements, multisite authorization expectations, canonical single-site and Network Admin navigation, random private export directories, and private filesystem permissions.

Installation

  1. Download the zip file
  2. Upload to your WordPress site through the Plugins > Add New > Upload menu
  3. Activate the plugin

Full Documentation