sirius-press/docs/plugin-compatibility.md
Silent Mode ddf49a5523 fix(sirius-press): recovering a key is not the same as verifying a signature
Running the fork against a live WordPress found a real hole in registration,
and it is the kind that only shows up when you actually try it.

ECDSA public-key recovery always succeeds. Given any well-formed signature
and any digest it returns a key — just not the signer's, unless the digest is
the one that was signed. The auth flow leaned on that as if a wrong message
would fail. It does not; it quietly yields a stranger's address.

At sign-in this was harmless, because the wrong address matches no account
and the attempt fails. Registration and wallet-linking were another matter:
both took the recovered address and bound it to an account, so a signature
over slightly different text — a challenge copied without its blank line, a
wallet that rewrote the text, a login signature replayed at the registration
form — created an account keyed to an address nobody could sign for. The
person would see "success" and discover the truth the next time they tried to
get in. Wallet-linking was worse still: it would move an existing account onto
a dead address and lock its owner out of their own site.

Both paths now require the address the signer claims and compare it to the
recovered one, which is what verification actually means. Sign-in accepts the
claim when the page sends it and uses it to turn "no account uses that wallet"
into the more useful "that signature is not over the text we asked for".

Also from running it:

URL rewriting mangled every link on a site whose URL carries a port. The
protocol-relative pass matched inside absolute URLs and gave each one a second
scheme, and matching the host without its port left the port stranded as
`//host:8760:8760/`. Local and staging installs would have exported a site of
broken links.

Plain permalinks silently collapse an entire site onto one exported file,
because every post's URL is `/?p=N` and its path is `/`. The queue looks
healthy the whole time. The Publishing screen now says so.

Translations loaded on `plugins_loaded`, which WordPress 6.7 warns about on
every request — the kind of noise that trains people to stop reading logs.

And one deletion: an `is_email()` filter written on the assumption that
WordPress rejects `.invalid` addresses. It does not — `is_email()` validates
syntax, not whether a domain could exist — so the filter never fired. A filter
that appears to relax a rule but does not is worse than no filter, because
someone later reasons from it. The documentation made the same claim and has
been corrected.

Verification added rather than asserted: tests/live.mjs drives a real instance
over HTTP (40 checks), and tests/mock-gateway.mjs answers uploads with the
signature check transcribed from the gateway's own source, so the publishing
path can be exercised without a registered name.
2026-09-21 02:35:32 +02:00

6.1 KiB

Plugin compatibility

The short answer: plugins work. Sirius Press changes how people sign in, not how WordPress works. Roles, capabilities, nonces, the options API, the REST API, the hook system and wp_mail() are all untouched, so a plugin that does not ask "what is this user's email address?" cannot tell the difference.

The rest of this page is the long answer, for the plugins that do ask.


The mechanism

Every account has a user_email. It is a placeholder under a domain ending in .invalid — a top-level domain RFC 2606 reserves precisely so that it can never resolve anywhere, ever.

This matters more than it sounds. $user->user_email is read in thousands of places across the ecosystem, usually without a null check. Leaving it empty would produce warnings, blank "From" headers and outright fatals in other people's code. Filling it with something permanently undeliverable means every one of those reads returns a string and behaves normally.

Mail addressed to one of those placeholders never reaches a mail transport. It is caught by pre_wp_mail and delivered to the Sirius Press inbox, where the recipient reads it while signed in. That is where password-reset mails, comment-moderation notices, update nags and "your plugin needs attention" messages end up.

Mail addressed to a real domain is passed straight through. If you configured SMTP, it sends. If you did not, it fails exactly as stock WordPress fails.

is_email() is left completely alone, and it is worth saying why, because the obvious guess is wrong. WordPress validates an address's syntax, not whether its domain could ever exist — so .invalid addresses already pass is_email() unchanged. No shim is needed to make the placeholders acceptable to plugins that validate, and none is shipped. (Verified against WordPress 7.1.1; an earlier draft of this fork carried a filter based on the wrong assumption, and it never fired.)

The flip side is that is_email() will also accept someone@example.invalid typed into a contact form by a visitor. That is stock WordPress behaviour, not something this fork introduced, and it is the contact form's business to care about.


The top ten

Three of these were installed and activated on a Sirius Press 0.1.0 instance running WordPress 7.1.1, with all four fork plugins active, and the whole wallet sign-in suite re-run with them loaded. Those rows say tested. The rest are reasoned from each plugin's setup and activation path and say expected — treat them as claims awaiting a test, and report anything that behaves differently.

Plugin Status Notes
Yoast SEO 28.6 tested — activates cleanly No errors on any admin screen with it loaded. Reads admin_email for schema output and gets the placeholder. Its XML sitemaps export to your name along with everything else.
Contact Form 7 6.1.7 tested — activates cleanly Forms build and save. The default recipient is the placeholder, so submissions land in the inbox — fine for a small site, not what you want for a real contact form. Set a real address and configure SMTP.
WooCommerce 11.1.1 tested — activates cleanly Store and admin work. Order emails to customers use the address the customer typed, so they send once SMTP is configured. One behaviour worth knowing: WooCommerce redirects subscriber-role accounts away from wp-admin, so a newly registered reader lands on the shop rather than their profile. That is WooCommerce's own setting, not this fork's.
Elementor expected to work No email dependency. Pages built with it export normally.
Wordfence expected to work Alert emails go to the inbox unless you set a real address. Its login-security features overlap with wallet auth; two-factor on top of a signature is redundant, and its "email me a code" option cannot work.
WP Super Cache / W3 Total Cache expected to work Compatible, but consider whether you need them: the static export already serves cached HTML from the name, which is the harder-working cache.
Akismet expected to work Needs an API key, obtained on akismet.com with a real address of yours. Nothing to do with site accounts.
Jetpack expected to work partly Connection requires a WordPress.com account. The modules built around subscriber email lists cannot do anything useful here. Not recommended.
UpdraftPlus expected to work Backups work. Report emails go to the inbox. Back up wp-config.php too, or you lose SIRIUS_PRESS_KEY and with it the stored publishing phrase.
Advanced Custom Fields expected to work No email dependency at all.

"Activates cleanly" means the plugin activated without a fatal or a WP_Error, and every Sirius Press admin screen plus Users, Profile and Plugins rendered with no PHP diagnostic while it was loaded.


What genuinely cannot work

Not because of a shim we did not write, but because the feature is the mailbox:

  • Email-based two-factor. There is no mailbox to send a code to.
  • Newsletter plugins managing your site's own accounts. A newsletter to a list of real addresses people typed into a form works fine. A newsletter to "all subscribers" does not, because subscribers have wallets, not mailboxes.
  • "Email me when someone comments" for account holders. Goes to the inbox instead, which the person sees next time they sign in.
  • Password reset by email. Deliberately removed. See accounts.md.

Making a plugin behave

If a plugin refuses to finish its setup because of a missing address, the usual fix is one filter:

add_filter( 'pre_option_their_email_setting', function ( $value ) {
	return $value ?: 'admin@' . SP_Settings::stub_email_domain();
} );

sirius-press-compat is a short file of exactly these. If you write one for a plugin not listed here, it is worth sending — the whole file is under a hundred lines of real code and it grows one plugin at a time.

If a plugin sends mail to a real address and it does not arrive, that is an SMTP problem rather than a Sirius Press one: see smtp.md.