Version 5.0.1

October 2, 2026

Security

  • The connect form can now only set the settings it shows. Before, every property of a source could be posted: sshOptions went straight onto the ssh command line, so anyone with the migrate permission could run a shell command on the web server with -oProxyCommand=…; read-only enforcement on a database source could be switched off; and sslCa could name any file.
  • The wp-cli source checks its host, user and port, ends ssh's options with --, and passes on only connection options (ProxyJump, ConnectTimeout and the like) as -o Key=Value — never ProxyCommand, LocalCommand or a bare flag, wherever they come from.
  • Media downloads and the REST source only fetch http and https URLs. Before, an attachment URL of file:///… in a WXR file imported a server file as a public asset. Each host — every redirect hop included — must resolve to a public address, and the connection is pinned to the address that was checked. A WordPress on your own network needs the new allowPrivateNetwork setting.
  • The REST source no longer follows a redirect to another host, which would have sent it the application password.
  • A re-submitted WXR form only keeps files from Passer's own upload folder. Before, any readable file on the server could be named and parsed as an export.
  • A WordPress user whose email matches an existing Craft account is linked to it for attribution, and the account is left alone. Before, the import renamed it and replaced its user groups — so a WXR file naming an admin's email could rename the admin, or move an account into a privileged group. Admins are never rewritten by a later run either.
  • WordPress roles can only be mapped onto user groups the person running the migration may assign users to. Other mappings are dropped, with a message, when the plan is saved or run.
  • Applying a provisioning plan needs an admin on an environment that allows admin changes, as creating sections, fields and groups always does in Craft. The dry run is unchanged.

Fixed

  • The plugin-free redirects destination wrote URL-rule arrays to config/redirects.php, which is the file Craft 5.6+ reads itself on every 404 — and refuses those keys, so every 404 on the site became a fatal error. It now writes Craft's own from => [to, statusCode] format, reports the redirects Craft can't express (regular expressions, query strings, the home page) instead of writing rules that never match, and leaves a config/redirects.php it didn't write alone, writing config/redirects.passer.php beside it to merge.
  • Saving the settings screen no longer drops settings that weren't on the form.
  • Deleting a plan and cancelling a run now ask first, and clearing the ID map's confirmation actually appears — it used an attribute Craft only honours on .formsubmit buttons.

Added

  • sourceOverrides in config/passer.php, for source settings the connect form doesn't offer — an ssh jump host, a database CA bundle — applied whenever that kind of source is used and never stored in a plan.
  • BaseImporter::EVENT_BEFORE_SAVE_ELEMENT / EVENT_AFTER_SAVE_ELEMENT, fired for every element any phase saves; a before handler can skip the record. Runner::EVENT_BEFORE_RUN / EVENT_AFTER_RUN around each run.
  • tests/integration/security.php: 55 checks, in-process and over HTTP as a non-admin migrator.
  • PHPStan and ECS configuration, with composer phpstan, check-cs and fix-cs.