Skip to content
PEWES ENERGY

Engineering

The WordPress nobody can reach

8 min readPEWES ENERGY

Make your mark notecard from the Mango Riot wrap

WordPress powers a large share of the web, which means it receives a large share of the attention. Most of that attention is automated: software that walks the internet looking for a login form, an outdated plugin, an exposed endpoint. It is not personal and it never stops.

Splitting rendering out of WordPress changes the shape of that problem in a way that is easy to state and easy to get wrong. The storefront is public. WordPress does not have to be. On this site it is not, and the work to get there is worth describing because almost nobody does it.

What a conventional shop exposes

On a normal WooCommerce site, the address a customer types and the address the administrator logs into are the same server. That server publishes a login form, an XML interface, a REST interface, the version numbers of its plugins in the markup, and a directory of files that tells an attacker precisely what is installed.

None of that is a flaw. It is how the software is designed to work, and it is fine for a blog. For a shop holding customer addresses and order history it is a lot of surface for something that only needs to be reachable by two parties.

The principle

In a headless build, exactly two things need to talk to WordPress: the application that renders the site, and the people who edit content. Everybody else, including every customer, only ever talks to the storefront.

Once that is true, WordPress can stop answering the public entirely. On this site it returns a plain not found to any address that is not one of those two. Not a forbidden page, which would confirm that something is there, but the same nothing a crawler would get from an address that never existed.

Getting there is mostly about links

The principle is one paragraph. The work is finding every place the address leaks, and there are more than anybody expects.

  • Images. Uploads live in WordPress and are served from it. Here they are published through the storefront’s own address instead, which also lets them be converted to modern formats at the size the layout asks for. The customer never sees where they came from.
  • Every link WooCommerce generates. Order confirmation, account pages, product permalinks. Each is produced by the commerce layer and each has to be redirected to the storefront.
  • Transactional email. This is the one everybody misses. The logo at the top of an order email is a link to the shop, built from the address WordPress thinks it lives at. So are the product thumbnails in the order table. On a stock install every single customer email carries that address in three places, and no amount of care in the site markup changes it.
  • Password reset. The link in that email has to land on a page the storefront owns, which means the storefront needs a reset screen and WordPress needs to be reduced to checking the key and writing the new password.
  • Structured data and feeds. Product images inside embedded data are just as much a leak as an image tag.

The only reliable way to find those is to render every outgoing email and every public page and search the result for the address. Reading templates and reasoning about them is how you convince yourself the job is done while three leaks are still live.

Obscurity is not a layer

This is the part worth being blunt about, because it is the assumption that quietly fails.

Putting WordPress on an address nobody publishes feels like protection. It is not. Every certificate issued for a hostname is written to a public, permanently searchable log. That log exists for good reasons and it means a hostname is public the moment it gets a certificate, whether or not anybody ever linked to it. On this site the access log confirmed it: automated scanners were requesting WordPress paths on that address before a single link to it existed anywhere.

So hiding the address is worth doing, because it keeps it out of the hands of customers and competitors and stops it being indexed. It is not worth relying on. The actual protection is the part that does not care whether the address is known: only known callers get an answer at all.

The rest of the surface

The data interface is not open. Every query the storefront can make is stored inside WordPress under a name, and the storefront asks for the name. An arbitrary query is refused. That removes a whole class of problem where a public data endpoint can be asked for more than it was ever meant to give.

The application authenticates as an application. It uses a credential that lives only on the server, never in a browser, never in a script sent to a visitor.

Customers never hold a token. Signing in produces a session in a cookie the browser cannot read and no script can reach. There is no token in local storage waiting for the first cross site scripting bug to find it.

The server stops introducing itself. Version numbers of the runtime were being announced in every response header. They are removed, because a version number is a shopping list.

What this costs and what it breaks

Two honest costs.

The first is that the development environment is now on the guest list too, and a home connection whose address changes means an afternoon of confusion until somebody updates one line. That is a real operational tax and worth knowing about before it happens rather than during.

The second is that a genuine exception has to stay open, because certificate renewal is validated over the same connection that is otherwise closed. Forget that and the certificate expires quietly in ninety days and takes the entire site with it. It is the kind of detail that separates a lock somebody configured from a lock somebody designed.

Why a buyer should care

Not because of a breach that has not happened. Because of what it says about the rest of the build.

An agency that has thought about which links leave in an order email has thought about the order email. One that has tested what an anonymous visitor receives from the administrative half of the system has tested the system. Security work of this kind is not glamorous, it is never in a portfolio screenshot, and it is the clearest available signal that the invisible parts were built with the same care as the visible ones.

It is also, for a European business, a compliance argument. Customer records held in a system that only two known parties can reach is a materially easier conversation than the same records in a system that publishes a login form to the entire internet.

The question to ask

If you are commissioning a headless build, ask one thing: after launch, what happens when somebody who is not you requests the WordPress address directly?

If the answer is a login page, the split was architectural rather than operational, and half the benefit was left on the table.

How to verify it rather than believe it

Every claim in this article is checkable, and the checking is the part that matters. Reading templates and reasoning about what they probably output is how a team convinces itself the job is finished while three leaks are still live.

Two sweeps find almost everything.

Render every email and search the output. Not the templates, the finished message, with a real order attached. On this site that meant generating every customer email the shop can send, order confirmations, dispatch notices, refunds, password resets, account creation, and searching each one for the address that is supposed to be hidden. Every single one contained it, in the header logo and in the product thumbnails, on a completely stock installation. A team that had only read the templates would have shipped all of them.

Fetch every public surface and search that too. Not just the pages. The sitemap, the robots file, the product feeds, the structured data, the web manifest, the service worker, the machine readable catalogue, and the public interface responses. Then look at what the browser itself requests while a real page loads, because a leak in the markup and a leak in a script are different bugs found by different methods.

The result of that sweep is a number, and the number should be zero. Anything else is a finding with an address attached, which is a far more useful thing to hand a client than an assurance.

What this does not protect against

It would be dishonest to let this read as a complete answer, because it closes one category of risk and leaves others exactly where they were.

It does nothing about a flaw in the storefront itself, which is public by definition. It does nothing about a weak password on an account that is on the guest list. It does nothing about a dependency with a vulnerability, in either half of the system. It does not remove the need for backups, for updates, or for somebody paying attention.

What it does is remove the entire category of attack that consists of automated software finding a login form and trying things. On a conventional installation that traffic is constant and it is the origin of most compromises. Taking the form off the public internet does not make the software perfect. It makes the software uninteresting to the machines that were looking at it, which is most of the threat for most shops.

A note on where this sits in a budget

All of the above is perhaps two days of work during a build, and most of it is finding rather than fixing. It is close to invisible afterwards and it will never appear in a portfolio.

It is also the part that is almost never done, which makes it a fair question to ask of anybody quoting for a headless project. Not whether they can, but whether they will, and how they intend to prove it.

This shop is a working demonstration of what a headless WooCommerce build is capable of, and everything described here is running on it. If you are weighing a build of your own, it was made by Pavels Web Solutions.

Back to the journal

Keep reading.

Engineering9 min read

A loyalty scheme with no points

No points, no wallet, no balance. Spend is measured two ways at once and the kinder answer wins, and twice a year a real voucher appears with your name on it. Here is why, and what it takes to make it correct.

Read the note

Engineering8 min read

A booking system is a scheduling system

The reseller call booking on this site is, almost exactly, what a consultancy sells and what a hotel sells. Here is what is underneath it, and what actually changes when you charge for the slot.

Read the note

Engineering8 min read

One catalogue, five readers

The same WooCommerce data on this site is read by the shop, a Google Shopping feed, a reseller API, AI assistants and search. One source of truth, and no duplication. That only works if it is decided early.

Read the note