tawasuyu · Making
Your WordPress, without WordPress: the same site to the pixel, without a single line of PHP
A WordPress site is a program that runs every time someone visits, with its database, its plugins and its admin doors open to the internet. qhatu takes it whole, identical to the pixel, and leaves nothing to execute: the same site, faster, and without a single line of PHP.
8 min readBy Gioser Editorial
Every time someone opens a page of a WordPress site, a program starts on the server: it reads the database, loads the theme, wakes up every plugin, builds the HTML and sends it. The same thing, over and over, for text that changes once a month. And all the while, that program leaves the doors it is administered through open to the whole internet. This article tells how qhatu —tawasuyu's content manager— takes a real WordPress site whole, leaves it identical to the pixel, and strips away everything that could be attacked.
The site that gets built on every visit
WordPress is the most common way to have a website, and for good reasons: it installs in an afternoon, there is a theme for everything, and a builder like Elementor lets you design without writing code. The price is paid later, in installments. Every plugin is someone else's code running with all of the site's permissions; every postponed update is an open door; and the database, the login and the API keep listening around the clock, even if the site is edited by one person twice a month.
The site everything here was tested on belongs to a real client: a retreat center built with WordPress, the WoodMart theme and Elementor Pro, with 64 pages, a gallery, a blog and two contact forms. An ordinary site, the kind there are millions of. The request was simple: keep it exactly the same, and keep it easy to edit.
One command
What was done by hand on the first day —downloading the rendered site, cleaning what WordPress appends to every file name, re-pointing the forms— is now a verb:
qhatu absorb-wordpress https://the-site.com/ folder
It reads the sitemaps WordPress publishes, downloads every page and everything it needs —stylesheets, scripts, images in all their sizes, fonts, icons—, and points every reference at the local copy. It skips what isn't content: the cart, the admin panel, the feeds, the searches. If an old address redirects to another, it does not duplicate it: it records it as a redirect for the server, so a link someone shared years ago still arrives. On the client's site:
| what | measured |
|---|---|
| pages | 64 |
| resources (stylesheets, scripts, images, fonts) | 448, 45 MB |
| time, with a courtesy pause between requests | 6 minutes |
| broken references in the copy | 0 |
Identical to the pixel, with a control arm
"Exactly the same" can be measured. The same page of the original and of the copy was opened in Chrome at 1366 by 2400 pixels, and the two captures were compared point by point. On the home page and on a blog post: 0 different pixels out of 3,278,400. For that zero to mean something, the test carries a control arm: each capture has 216,987 distinct colors, so these are not two blank pages being compared.
Honesty includes what did not come out as zero. On a third page, 1,785 pixels differed in the menu strip. Before blaming the copy, the original was compared with itself, twice: 1,642 and 0. It is the page itself, which does not paint the same way twice in a row.
Nothing to hack
A WordPress scanner always starts the same way: it asks for the addresses every WordPress has. That same question was put to both sites, in passive mode —read requests to public addresses only, without trying a single password—:
| address | what it is | WordPress | copy |
|---|---|---|---|
/wp-login.php | the login door | answers | 404 |
/wp-admin/ | the admin panel | answers | 404 |
/xmlrpc.php | the classic door for batch brute force | answers | 404 |
/wp-json/ | the API | answers | 404 |
/wp-json/wp/v2/users | the list of who can log in | answers, with 3 names | 404 |
/?author=1 | the first author's user name | tells it | no |
/readme.html | the exact version | answers | 404 |
/wp-admin/install.php | the installer | answers | 404 |
/wp-cron.php | tasks anyone can trigger | answers | 404 |
Nine out of nine against zero out of nine. On top of that, the original announced in its own code the exact version of WordPress and of two of its plugins: exactly what an attacker looks for to cross against a list of known flaws. It is not that the copy keeps those doors better shut: it doesn't have them. There is no PHP to run, no database to query, no users to guess.
Measuring this turned up a detail worth telling. The copy still said "WordPress" in an invisible tag in its HTML, inherited from the original: a scanner read it and announced a WordPress that no longer existed. The absorber now removes it.
And the contact forms, which in WordPress are one more plugin? On the copy they are handled by a 205 KB WebAssembly function that runs on the server with declared capabilities: it can store what arrives and read the clock, and nothing else. It cannot read the disk or reach the network; what it didn't ask for doesn't exist for it. The messages are read in the panel, and download as a spreadsheet.
Faster, and measured without cheating
Five pages, five cold loads on each side —a fresh browser per load, no cache—, alternating original and copy so a bad stretch of network doesn't land entirely on one side. The medians:
| page | first byte | largest paint | full load |
|---|---|---|---|
| home | 2.5 s → 0.9 s | 6.1 s → 4.9 s | 9.9 s → 5.9 s |
| service page | 1.9 s → 0.8 s | 6.5 s → 3.8 s | 14.9 s → 5.4 s |
| long page with gallery | 2.3 s → 0.9 s | 11.7 s → 6.3 s | 22.3 s → 7.9 s |
| blog listing | 2.4 s → 0.7 s | 4.5 s → 4.1 s | 5.1 s → 5.3 s |
| a blog post | 2.3 s → 0.8 s | 5.9 s → 2.5 s | 7.0 s → 5.2 s |
The bytes that travel are practically the same: it is the same page. What changes is the server. And there is a caveat an honest article has to make: the two sites live on different servers, in different places, and the copy was closer to where the measurement was taken. So the network was separated from the software. Opening the connection costs 392 milliseconds to the original and 179 to the copy; that is distance, not code. Subtracting one round trip on each side, what is left is the work of building the page:
And it is still edited, in place
A static copy you can't touch would be a museum. qhatu's is edited from a panel that lives at its own address —nothing on the public site links to it—, on the page itself, as it looks:
- Click on a text and type. A bar offers bold, italics, links, lists, headings and quotes, and the colors are the site's own, not an arbitrary picker. Pasting from Word or Google Docs keeps the formatting and throws away the junk.
- Click on an image and replace it. Picking several at once adds photos to a gallery: each extra one duplicates the item and gets its own picture.
- Duplicate, move and delete any block: a card, a section, a list item. And a new page is a copy of another, at whatever address you want.
- The site's palette on the side. The colors the page uses, counted; change one and see it instantly, and a button applies it to the eighty-odd stylesheets Elementor left behind. If a text stops being readable over its background, the panel says so, with the contrast measurement in hand.
- Publishing is a signed act. Saving changes the folder; publishing turns the site into a new, signed version that the server serves whole or not at all. Going back to the previous one is a button. And every save also keeps the previous version of the page, just in case.
All of that runs on saywa, tawasuyu's web server. On the machine where this site lives, saywa and the project's own forge have already replaced four programs: the web server, the git forge, the proxy and the PHP interpreter. Someone wrote both pieces from scratch, in Rust, with a rule that repeats across the whole project: what wasn't measured isn't claimed.
What it doesn't do yet
- The absorber doesn't run JavaScript. It takes what the server sends; a site that builds its content only in the browser comes out half done. The one in this article, like almost all of WordPress, sends the HTML already built.
- There is no store yet. WooCommerce's cart and checkout are not absorbed: they are programs, not pages. qhatu's own store —no database, with the price recalculated on the server— is in the plan, and it is the next thing for this same client.
- No comments, no search, no logged-in areas. A membership site isn't absorbed this way.
- Editing isn't designing from scratch. Today you modify what the site already has; its own blocks —cards, carousels, galleries— and themes made of values instead of code are written into the plan and not built.
- Fonts can't be changed from the palette, only colors.
- The forms don't send email. Messages land in the panel; sending a notice requires functions to reach the network, and that capability doesn't exist yet.
- Going back changes the site, not the folder. If you publish again without touching anything, what was undone comes back; to recover a page there is the saved version.
- The speed numbers are from one day and one place. That is why the network is separated out: it is the part that doesn't depend on where you measure from.
How to see it
qhatu lives in the tawasuyu repository —git.tawasuyu.net, on its own server— under 02_ruway/qhatu, and saywa under 03_ukupacha/saywa. The two instruments behind this article, the exposure probe and the speed one, are in scripts/qhatu-careo/: anyone can run them against their own WordPress and its copy, and compare.