Security
Middleware is not authorization
Four NestJS advisories in seven months, all one bug: the matcher and the handler disagreed about the request. The check belongs where the data is read.
The first assumption to drop is that your site is too small to be interesting. Nobody reads your project's name before attacking it. What reaches your site are automated scanners probing known paths, reading a plugin version out of a readme.txt, and matching it against a vulnerability published yesterday. The gap between disclosure and mass exploitation is measured in hours, not weeks.
The motive is usually not your data either. It is a server that sends spam, phishing pages hidden in an uploads directory, and links planted in your content. Which means the attacker does not want to break the site. They want it to keep working while you fail to notice.
Most compromises use nothing clever. They use a plugin that has not been updated in a year.
No PHP execution in upload directories. The exploit has two halves: upload a file, then call it. Break the second half:
# wp-content/uploads/.htaccess (Apache 2.4)
<FilesMatch "\.(php|phar|phtml)$">
Require all denied
</FilesMatch>File permissions. Directories 755, files 644, wp-config.php at 640. 777 is not a fix for a permissions problem; it is a postponement with a price.
No file editing from the dashboard. The theme editor turns any stolen admin session into direct code execution:
define('DISALLOW_FILE_EDIT', true);xmlrpc.php. If you do not use the mobile app or remote publishing, block it. Its system.multicall lets an attacker try hundreds of passwords in a single request.
The database account. One user per application, privileges limited to its own database. Never GRANT ALL ON *.*. When one application is compromised, the rest should not be.
Stolen passwords are more common than technical vulnerabilities. Enable two-factor authentication on every administrative account, no exceptions. Delete accounts for people who have left rather than disabling them. Rate-limit login attempts — not because it stops guessing, but because it makes guessing expensive and visible in the logs. And do not share one account between three people: the day you need to know who did what, you will not be able to.
HTTPS everywhere with HSTS, and session cookies marked Secure, HttpOnly and SameSite. Then the cheap headers there is no excuse for omitting:
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy-Report-Only: default-src 'self'Start a content security policy in report-only mode. An enforced policy written in a hurry breaks the site, gets removed entirely half an hour later, and leaves you with nothing.
A backup you have never restored is not a backup. It is a large file you think well of. Do one full restore onto a test environment, and write down how long it took. Keep a copy off the server itself: whoever holds your account holds every backup inside it.
Start from the assumption that every credential on the server is stolen: database passwords, API keys, SSH keys, session salts. Rotate all of them. Do not stop at deleting the malicious file you found — whoever uploaded it usually uploaded others. The cleaner and faster path is to reinstall core and plugins from their original sources, restore only your content, and close the hole they came in through. If you have not found that hole, you are not finished, however clean the site looks.
Security
Four NestJS advisories in seven months, all one bug: the matcher and the handler disagreed about the request. The check belongs where the data is read.
Security
One RCE at CVSS 10.0, then six denial-of-service patches in eight months. When the contract is "accept whatever the format encodes", fixing one lever leaves the rest.
Security
Eighteen malicious versions of one package went out in two and a half hours, harvesting tokens on install. Lockfiles, disabled scripts and short-lived credentials.