The last post covered architecture-level decisions. This one’s narrower: specific choices inside the code itself, the kind of thing that only shows up if you actually read the implementation rather than the feature list.
1. CSRF protection during a window where nonces don’t apply
WordPress’s nonce system is built around an authenticated user context. During the gap between a correct password and a completed 2FA check, there isn’t one yet. The user has passed the first factor but isn’t logged in. That’s an easy place to accidentally skip CSRF protection entirely, because the standard tool genuinely doesn’t fit.
The actual implementation: a random 32-character token is issued server-side, stored in a transient, and set as a cookie on the browser (httponly, secure when the site’s on HTTPS, SameSite=Lax). When the 2FA form submits, the token in the POST body has to match the token in the cookie, checked with hash_equals(). That specific design matters: it’s not enough for an attacker to obtain the token value alone, say, from a referrer header, shared-computer browser history, or a server log. They’d also need to hold the actual cookie the legitimate browser was issued. Knowing the value isn’t sufficient; possessing the cookie is required too.
2. hash_equals(), applied consistently, not just once
Comparing secrets with == or === leaks timing information. A sufficiently patient attacker can use response-time differences to guess a secret byte by byte. This is well-known in security circles and routinely skipped anyway, because it’s invisible in normal testing and only matters under a specific kind of attack most developers never simulate.
It shows up everywhere a secret gets compared in this codebase: the CSRF token above, WebAuthn challenge and origin verification, relying-party ID hash checks, trusted-device token lookups, WebAuthn credential ID matching, TOTP code verification, MD5 checksum comparisons during file-integrity verification, and the hash-chain check that makes the audit log tamper-evident. Not applied once and forgotten. Applied as a standing rule everywhere a comparison result could matter.
3. WebAuthn and CBOR, implemented from raw bytes, and validated, not just asserted
There’s no vendor/ directory, no Composer dependency, no third-party WebAuthn framework anywhere in this codebase. Challenge verification, origin binding, relying-party ID hash confirmation, and signature verification (openssl_verify(), called directly) are all implemented against the raw binary attestation and assertion data itself, including a custom CBOR decoder for the authenticator data structure.
That decoder is whitelist-only, not “handles most things and rejects a few.” It accepts exactly six CBOR constructs, the ones a WebAuthn attestation object or COSE key actually contains, and nothing else, not skipped, not best-effort decoded, rejected outright. A hard nesting-depth limit and a hard input-size limit are both checked before parsing even begins. Every CBOR structure it ever decodes comes from the browser’s own platform authenticator API, not arbitrary user input, and the parser now assumes nothing about that beyond what’s actually needed.
Here’s the part that matters more than the design intent: it’s been checked, not just reasoned about, twice.
Concrete verification (after the second hardening pass):
Verification performed
- Official conformance vectors. All in-scope values from RFC 8949 Appendix A (fetched live from the official CBOR test-vectors repository) decode correctly. Constructs outside the whitelist, floats, tags, indefinite-length items, are rejected unconditionally, not leniently handled.
- Real authenticator data. A genuine attestationObject and assertion captured from a macOS Touch ID authenticator (fixtures from the duo-labs/webauthn library) were processed end-to-end through the production parsing path, re-run after the whitelist was tightened. Extracted credential ID, signature counter, and public key still matched exactly. A separate, fully synthesized registration, built from a freshly generated real ECDSA key pair, was driven all the way through to a genuinely successful stored credential, confirming the whole pipeline still works, not just the parsing step in isolation.
- Hand-crafted adversarial inputs. Truncated strings, truncated integers, oversized input, excessive nesting depth, and reserved encodings all now throw explicitly, checked at the exact boundary of each new limit.
- Mutation fuzzing. 200,000 fresh iterations against the hardened decoder. Zero cases where it threw anything other than a clean, catchable exception, the exact failure mode the earlier hardening pass exists to close.
- Regression testing. Full suite re-run after every change. Zero regressions.
Outcome
All identified parsing defects fail closed. Across both rounds of testing, nothing reached the cryptographic checks that should not have: no forged signatures, no forged credentials, no bypassed challenge or origin validation.
Yes, this invites the standard “don’t roll your own crypto” objection, and that instinct is usually right. Worth being precise about what this actually is: it’s a binary-format decoder, not a cryptographic primitive. Signature verification itself goes through openssl_verify(), PHP’s own battle-tested crypto extension, not homemade math, now isolated into its own small function specifically so that separation is easy to verify by reading it, not just by taking the design description on faith. The decoder’s job stays narrower still: turn bytes into a structure without lying about what’s in them, without crashing on something malformed, and now, without accepting anything it was never built to handle in the first place.
4. Two different hash algorithms, chosen for two different threat models
Device tokens (high-entropy, randomly generated, never typed by a human) are hashed with plain SHA-256. Fast, and appropriate, because there’s nothing to realistically brute-force against a token that long.
Backup codes are different: 48 bits of entropy (6 bytes, 12 hex characters), deliberately short enough for a human to type by hand. Hashed with wp_hash_password() instead, WordPress’s own bcrypt-based, deliberately slow hashing function. If a database ever leaked, a fast hash against a 48-bit space is a meaningfully more attackable target than the same space behind a slow one. Two different secrets, two deliberately different algorithms, not one hash function applied uniformly out of convenience.
5. Every secret comes from a CSPRNG, not mt_rand()
Backup codes, TOTP secrets, and the CSRF-substitute token above are all generated with random_bytes(), PHP’s cryptographically secure random source, never mt_rand() or anything weaker. It’s a small thing to check and an easy thing to get wrong, so it’s worth just saying plainly: it’s consistent everywhere a secret gets minted, not just in the places that are obviously security-critical.
6. A documented lockout-risk assessment, not just a feature list
The module that hides the default login URL calls itself out, in its own code comments, as “the single highest lockout-risk feature in this entire plugin, a bug here can affect the site owner, not just an attacker.” It’s the only hardening feature that defaults off rather than on, deliberately. And rather than reimplementing a login form, a whole extra attack surface to get right, it intercepts the request early and hands off to WordPress core’s own unmodified login logic. Reuse over reinvention, specifically because the blast radius of getting a homemade login form wrong is worse than the blast radius of not hiding the URL at all.
7. A real emergency kill switch, respected everywhere
A wp-config.php constant instantly restores normal access if someone locks themselves out, checked consistently across every module in the plugin capable of blocking access, not bolted onto just one as an afterthought. The detail that matters: it can only be set by editing wp-config.php directly, which needs actual server file access. It’s not exposed anywhere in the WordPress dashboard, so it can’t be flipped by an attacker who’s merely compromised an admin account.
8. Vulnerability scanning does real version-range comparison, not string matching
Installed plugin/theme versions are checked against known-vulnerable ranges using PHP’s version_compare() with proper operators, not a naive string comparison. That distinction actually matters: as strings, “4.9” > “4.10”, which is wrong as a version comparison, and a vulnerability scanner that gets this wrong either misses genuinely vulnerable versions or flags already-patched ones as vulnerable. This one handles it correctly.
9. Exemptions are field-based, not role-based
The pattern-matching request inspector deliberately doesn’t exempt trusted users wholesale from its checks. Its own code comments explain exactly why: “a person’s privilege level says nothing about whether a specific piece of submitted data is safe. A compromised low-privilege account, or a stored XSS payload riding through a legitimate editor’s own session, would sail straight through a role-based exemption untouched.” So instead of exempting people, it exempts specific fields known to legitimately carry rich HTML (post content, widget text, page-builder fields) by name, regardless of who’s submitting them. A compromised editor account gets exactly the same scrutiny as an anonymous visitor.
It’s also explicitly not called a WAF anywhere in the code or UI, on purpose: a real WAF is backed by a team continuously updating signatures as attackers develop new bypass techniques, an ongoing arms race, not a fixed target. A static local pattern list will catch today’s textbook attack strings and won’t catch tomorrow’s obfuscation. Only narrow, high-confidence signatures (an actual <script> tag in a URL parameter, UNION SELECT with SQL comment terminators) can optionally hard-block, off by default, and everything else, ambiguous or borderline, never blocks anything by itself. It only contributes weighted points to the same threat-score system every other detector feeds, so a single false positive stays harmless.
That same precision-over-convenience principle got applied harder to one specific corner of this scanner recently: cookies. WordPress’s own auth and session cookies are exempted from inspection here too, for the same reason post content is, they legitimately contain hashes and pipe-delimited data that would otherwise look suspicious. The exemption used to match by name prefix: any cookie simply starting with, say, “wordpress_test_cookie” skipped scanning entirely. A cookie’s name is exactly as attacker-controllable as any other request field though, so a cookie deliberately named “wordpress_test_cookie_<script>evil</script>” would have skipped scanning purely because of how it started, not because of anything about what it actually contained. Fixed by matching the real, fixed shape a genuine WordPress cookie has, a hex hash of a specific length, a numeric user ID, not just its opening characters. Confirmed against both directions afterward: the crafted bypass attempt is now caught, and every real WordPress cookie shape is still correctly left alone.
10. Anomaly detection that catches what per-IP rate limiting structurally can’t
Per-IP, per-username brute-force lockout is genuinely effective against a normal attack, but structurally blind to a distributed, slow one: many different IPs, many different usernames, each individually staying under any per-entity threshold. This compares the site’s total failed-login rate for the current hour against its own rolling 7-day baseline instead, so a coordinated attack shows up as a spike in the aggregate even when no single IP or account ever crosses any per-entity limit.
It’s explicitly alert-only, and says so in its own comments: it has no way to know which of many failing logins are the attack and which are ordinary human typos, so it can’t safely take any blocking action on its own, only flag “this hour’s failure rate is unusual, go look.” A minimum floor (20 failures) keeps a quiet site’s near-zero baseline from tripping on a handful of typos, and a six-hour cooldown means one alert per episode, not one every hour it stays elevated.
11. Verifying Facebook’s crawler by actual network ownership, not just hostname
Every other verified crawler here (Googlebot, Bingbot) is confirmed the way Google and Microsoft themselves document: reverse DNS, then forward-confirmed. Facebook’s link-preview fetcher doesn’t reliably support that same method, so it’s verified differently: a reverse-octet DNS query against Team Cymru’s public IP-to-ASN lookup service, confirming the IP actually belongs to Meta’s own Autonomous System. The same kind of technique real network operators use to answer “who actually owns this address block,” not a web-application-level check. No API key, no account, a single DNS TXT lookup. It also correctly handles the case where a single announced address block lists multiple ASNs, checking each one individually rather than assuming the first, and it’s honest in its own comments about the one real limitation: this specific lookup path doesn’t support IPv6.
12. A timing correlation gets treated with less certainty than a hash match, on purpose
A modified WordPress core file and a new administrator account created within 12 hours of each other is a well-known real attack pattern: plant a backdoor, create a hidden admin account around the same time. This plugin checks for exactly that correlation, off by default, and its own comments are explicit about the distinction that matters: “a timing correlation between a new admin account and a suspicious file is a signal worth acting on fast, but it is NOT the same kind of certainty as a checksum match. There are innocent explanations (a real new hire, a coincidental auto-update).”
So the response is scaled to match that lower certainty. It never deletes the account. It strips it down to the lowest-privilege role and records exactly what role it had, so a false positive is one click to undo through the same magic-link restore mechanism covered earlier, not an unrecoverable action. It also won’t fire during local development, and it respects the same wp-config.php kill switch as every other blocking module in the plugin. A provable hash mismatch gets a stronger, more automatic response than a plausible-but-unproven correlation. Worth noticing that the code treats those two kinds of evidence differently, because a lot of security tooling doesn’t.
13. The vulnerability scanner refuses to guess when it isn’t sure
Every finding starts from real, sourced vulnerability data, but not every source clearly states the exact version range a fix applies to. Rather than assume a plausible-looking finding is confirmed, the scanner checks whether the affected-version range can actually be determined with confidence. If it can, the finding is marked confirmed. If it can’t, it’s labeled unconfirmed instead, surfaced for a human to look at, not silently asserted either way.
This is a different thing from getting the version comparison mechanically correct (that’s point 8). This is about what happens when the underlying data itself doesn’t clearly support a conclusion, refusing to convert genuine uncertainty into false confidence. The same instinct shows up in the file-integrity module’s handling of a timing correlation versus a hash mismatch (point 12), just in an entirely different part of the codebase: know the difference between what you can prove and what merely looks likely, and say so honestly in the output rather than flattening both into the same result.
14. Watching WordPress’s own file editor, not just recommending you disable it
WordPress ships a built-in Plugin Editor and Theme Editor, reachable the instant anyone has any authenticated access, even a leaked low-privilege login. It lets someone paste a webshell directly into a real PHP file and save it, no additional exploit required. Standard advice is “just disable file editing” (DISALLOW_FILE_EDIT), correct but blunt: it doesn’t tell you if the editor was already used before you thought to check, or catch a legitimate admin who still has it enabled.
This hooks the exact same wp_ajax_edit-theme-plugin-file action WordPress core itself uses to process a save, at priority 5, ahead of core’s own handler registered at the default priority 10 on that same hook. That ordering matters specifically: it captures the file, the editing user, and a real byte/line-count diff before core’s handler saves the file and terminates the request, a callback registered after core’s would simply never run, since core’s own handler ends the request first. It doesn’t duplicate core’s nonce check, deliberately, core’s own verification still runs a moment later in the same request and rejects anything forged regardless of what this watcher does; duplicating that check here risks either creating a bypass if done wrong or breaking legitimate alerts if done too strictly. Alert-only by default, since a legitimate admin might genuinely need this editor for a real emergency fix, with an off-by-default option to hard-block the save outright for anyone who wants it.
15. Recognising the shape of a reconnaissance sweep, not just a hit on a bait file
Canary and the admin honeytoken catch someone hitting a specific fake path this plugin planted. They never fire on anything else, which means a real scanner checking ten genuinely different, real, information-revealing paths, a WordPress version fingerprint, an exposed debug log, a .git/config left readable, anonymous user enumeration through the REST API, sails through completely untouched, because none of those are bait, they’re real endpoints being checked for real information.
This targets the pattern itself instead: if the same IP hits three or more genuinely different fingerprinting-relevant paths within 90 seconds, that’s recorded as its own signal, deduplicated by distinct path rather than raw request count, since hitting the same path five times is one check, not five, and hitting five different paths is what actually makes it a sweep. The watched-path list is deliberately scoped to timeless information-revealing targets, not specific plugin CVE paths, which would go stale within months and aren’t really reconnaissance behaviour in the same sense, that’s the vulnerability scanner’s job, not this one’s.
16. A boot-order trick that closes the gap auto_prepend_file left open, without its failure mode
An earlier version of this checked known-bad IPs by having the server run a directive before WordPress even loaded. It got dropped: if that file path ever went stale, the entire site fataled on every request, including the login page, with no WordPress-level recovery.
The replacement uses a WordPress mechanism most people don’t think about: files in wp-content/mu-plugins load automatically, before any regular plugin or theme, but critically, after wp-config.php has already loaded. That ordering is what makes it safe: the plugin’s own emergency kill switch is already defined and checkable by the time this runs, something that was structurally impossible with the server-directive approach. The generated loader also checks its own target file exists before doing anything and fails silently if not, a normal file in wp-content, fixable by FTP or any host’s file manager, not a server config directive most people don’t know exists. Deliberately scoped small: it only checks the manual “always block” list, the smallest, highest-confidence dataset available, not the full country-range or threat-intel system, which would mean loading meaningfully more data before WordPress itself has finished booting.
17. A malware scanner built on PHP’s own tokenizer, not a regex
A text search for “eval(” is trivially evaded: extra whitespace, a comment inserted between the function name and the parenthesis, either still executes identically but slips past a naive pattern. PHP’s own tokenizer doesn’t care about whitespace or comments between tokens, it sees the same token stream the language actually executes, so matching against that is meaningfully harder to evade with simple formatting tricks.
Static analysis only, deliberately, there’s no way to intercept eval() as a language construct from userland PHP, that would need a compiled extension. This scans file content, at the moment file integrity scanning finds something unexpected, feeding directly into the existing quarantine workflow. Three separate checks, each scoped to a different confidence level: eval() or assert() with a request superglobal nearby is treated as high confidence, a genuine near-unambiguous signature; a dangerous function invoked indirectly through a variable is treated as medium; a chained decode/decompress pattern (base64_decode wrapped in gzinflate, a common payload-obfuscation technique) is treated as low, since legitimate code occasionally does something similar for real reasons.
18. A rate limiter that tries three backends in order, and is honest about why each one exists
WordPress transients fall back to the database whenever no external object cache is configured, which is most ordinary hosting. Under a real burst of traffic, every rate-limit check becomes a database read or write, right when that’s least wanted.
This tries APCu first (common even on budget shared hosting, verified with an actual round-trip store-and-fetch, not just a function_exists() guess, since the functions can exist but still be non-functional), falls back to file-locked counters in the system temp directory if APCu isn’t available, and only touches the database as an explicit last resort. The file-based tier is worth a specific note: it uses raw file handles and flock(), deliberately, not WordPress’s filesystem abstraction, because that abstraction has no locking primitive at all. The lock is what stops two simultaneous requests from both reading the same count, both incrementing independently, and both writing back the same value, silently losing an increment during exactly the kind of concurrent burst this exists to measure accurately.
19. A behavioral check scoped to the one browser family it actually applies to
Modern Chrome, Edge, and other Chromium-based browsers send a set of Client Hints headers (Sec-Ch-Ua) on nearly every request. Firefox and Safari never do, by design, regardless of how genuine the visitor is. A check for “is this header missing” only makes sense if it’s first confirmed the request is even claiming to be a browser that would send it in the first place, applying it universally would flag every real Firefox and Safari visitor as suspicious.
So this only runs when the User-Agent string itself claims to be Chromium-based. A UA claiming a recent Chrome version but missing the Client Hints header entirely is a real, specific mismatch, faking a UA string is trivial, replicating the full Client Hints handshake isn’t something most simple scraping tools bother implementing.
20. Detecting the shape of an object-injection attempt without asserting which gadget chain it is
PHP Object Injection needs attacker-controlled data to reach unserialize(), where a class’s __wakeup() or __destruct() method becomes a gadget chain doing something unintended. WordPress’s own coding standards specifically discourage passing serialized data through request parameters for exactly this reason.
Deliberately not built around a hardcoded list of “known vulnerable classes.” Which specific classes have exploitable chains changes over time and depends on exactly what’s loaded on a given site, a fixed list checked once would either miss real ones or go stale within months, and asserting a list as current without being able to verify it against a live, maintained source would be exactly the kind of overclaiming avoided everywhere else in this plugin. Instead, it looks for the structural signature itself, a serialized PHP object header, distinct from a serialized array, which can’t trigger the same behaviour, appearing in raw request input. Cookies are deliberately excluded from the scan, WordPress’s own session and auth cookies are themselves serialized in places, scanning them would mean constantly flagging the site’s own legitimate traffic rather than anything genuinely suspicious.
That structural signature originally meant one specific thing: the “O:” marker PHP uses for a plain object instance. There’s a second one though, “C:”, the marker for a class implementing PHP’s Serializable interface, whose own unserialize() method gets called directly and is exactly as capable of being a gadget chain as “O:” is. The scanner missed it entirely. Confirmed with real PHP, not a hand-typed guess at the format: generating actual serialized output from a Serializable class and running it through the scanner showed it sailing through undetected. Now both markers are checked, confirmed against that same real payload, this time caught.
21. A honeypot that survives the perimeter being breached
Every trap covered above (the canary paths, the admin honeytoken) catches someone before they’re in: a fake login URL, a hidden wp-admin link nobody legitimate would ever click. All of that goes dark the moment an attacker is genuinely inside, because a stolen session or a compromised staff login isn’t scanning bait paths anymore, it’s browsing real data through a real, authenticated door.
This plants the trap on the other side of that door instead. A small number of decoy posts get created: real rows in the database, deliberately excluded from every path a real visitor or well-behaved integration could ever reach them through, public queries, REST listings, sitemaps, search. Nothing links to them anywhere. The only two ways to ever land on one are to guess or enumerate a post ID directly in the wp-admin editor, or fetch one directly by ID through the REST API. Neither has an innocent explanation, which is what makes this a different kind of signal from a heuristic: not “this looks unusual,” but “there is no legitimate reason this happened at all.” Same reversible, human-reviewed philosophy as the honeytoken it sits alongside: it never bans or logs anyone out automatically, it just makes sure a human finds out immediately when something touches data that doesn’t exist for anyone.
22. A second signal under the passkey, for the question a passkey can’t answer
A passkey or a correct 2FA code proves someone holds the right device or authenticator. It doesn’t prove it’s still the account owner holding it five minutes later on an unlocked laptop someone walked up to. Nothing password- or key-based can ever answer that question, because it isn’t a “what you have” problem.
The 2FA challenge screen now quietly compares how this particular login behaved, the rhythm of keystrokes entering the code, or the mouse movement and time-on-screen on a passkey login where there’s nothing to type, against that same account’s own established rhythm from past logins, using a simple running mean/variance per account, not a trained model or a third-party service. This is deliberately treated as a weak, noisy signal, not a verdict: people’s own typing varies between devices and moods, so it never blocks or alerts anything on its own, and it doesn’t start comparing at all until an account has at least five past logins to build a real baseline against. It only ever contributes one more weighted point into the same threat-score system every other detector already feeds, where a single instance is noise, but a mismatch alongside other signals becomes something a human can actually see and act on.
23. A restore that refuses to happen without a way back
Restoring a core file automatically, the moment a scan finds it doesn’t match WordPress.org’s official checksum, sounds like the obvious move. It’s also the kind of feature that can quietly do real damage: a “modified” finding doesn’t always mean a hack. It could just as easily be a host patching core files for its own reasons, and overwriting that with no way back turns a false positive into data loss.
So the restore fetches the exact official file from WordPress.org’s own release archive for the exact installed version, checksum-verifies it before writing and again after, the same discipline covered under point 3. What’s new is what happens immediately before any of that: the file currently on disk gets backed up first, into a directory with the same execution-blocked protection quarantined files get, and if that backup can’t be made for any reason, the restore refuses to proceed at all rather than overwrite and hope the original was never worth keeping. A “Restore original” button then undoes any restore in one click, checked the same way the restore itself was. Backups expire on their own after a configurable window, so this isn’t an unbounded pile of old file copies sitting on the server forever either.
Nothing here claims the automatic restore is now risk-free. It means getting it wrong stopped being permanent.
24. The same bug, hiding in six places, until someone went looking for all of them
Country blocking, datacentre blocking, and the manual allow/deny lists all rest on the same basic operation: parse a CIDR range like “192.168.1.0/24” and decide whether an IP falls inside it. That parsing has one sharp edge, the “/24” part has to be a genuine, well-formed number. Cast a malformed one straight to an integer the lazy way, and PHP doesn’t error, it silently gives you zero. A “/0” prefix doesn’t mean “match nothing.” It means the entire address space. A single typo, or one corrupted line in a downloaded threat-intel feed, could silently turn “block this one address” into “block every visitor to the site,” or the reverse, depending on which list it landed in.
That bug wasn’t a one-off. Checking it properly meant finding every place in the codebase that parsed a CIDR range at all, not just the obvious one. It turned up independently, hand-copied, in six separate spots across both the free and paid codebases: the shared parsing utility, the manual list matcher, the country-blocking matcher, and the mu-plugins loader that runs before WordPress itself has finished booting, the earliest, most privileged check in the entire plugin, where this exact failure mode would have meant every single visitor blocked with nothing downstream able to override it. Each copy had drifted there independently over time and needed to be checked and fixed on its own, since patching one does nothing for the other five.
Fixed at the source in each. Two of the copies, the manual list matcher and the country-blocking matcher, were then rewritten to call the shared, tested parsing code directly instead of keeping their own independent version, specifically so the same bug can’t quietly grow a seventh copy somewhere later without the shared code catching it first. The mu-plugins loader couldn’t be refactored the same way, it’s deliberately self-contained by design, so it doesn’t have to load the rest of the plugin just to run this one early check, so it got the identical fix applied on its own.
Verification here went one step further than usual: rather than trust that fixing the template that generates the mu-plugin loader was enough, the actual generated file was pulled out and run as a real, standalone PHP process, the literal code a live server executes, against real scenarios including the exact IP address a genuine canary-trap detection produced on this site a few days earlier. Country-range matching was checked against real, published ranges too, actual Google DNS blocks among them, confirming legitimate traffic still matches correctly and nothing else quietly broke while this was being fixed.
25. One careful IP check, and five places quietly not using it
This plugin has exactly one function that resolves a visitor’s real IP address carefully: trust REMOTE_ADDR by default, the actual TCP connection address a visitor cannot forge, and only consult a proxy header like CF-Connecting-IP if the site owner has explicitly said to and named their specific proxy. Get that wrong the naive way, trust a header unconditionally, and any visitor can claim to be any IP just by setting it themselves. That care is well established in the codebase. It just wasn’t universal.
Brute-force login lockout had its own separate, simpler copy: REMOTE_ADDR, full stop, no awareness of the proxy setting at all. On an ordinary standalone site that’s harmless, REMOTE_ADDR is already correct. Behind a CDN or reverse proxy with the trust-proxy setting turned on though, REMOTE_ADDR stops being the visitor’s address and becomes the proxy’s own, identical for every single person passing through it. Lockout keyed to that address doesn’t lock out an attacker anymore. It locks out everyone behind the same edge, the moment any one of them fails a login five times, guilty or not. A protection meant to stop one bad actor was capable of quietly locking out an entire site’s worth of real visitors, on exactly the hosting setup, behind a CDN, that a security-conscious site owner is most likely to be running.
The same question that found the CIDR bug applied again here: is this the only place doing this, or did it get copied. It wasn’t the only place. Five more spots across the paid product were resolving IP the same naive way, for a threat-score signal, a honeytoken trip log, a phantom-record trip log, a login audit entry, an account-notification email. None of those five lock anyone out on their own, so the damage was smaller, a wrong IP address recorded somewhere a human reads it later rather than a real visitor turned away, but it was the identical root cause showing up in six places instead of one.
Fixed the same way the CIDR bug was: one shared, tested function added specifically for this, and all six call sites moved onto it instead of keeping their own copy. One exception, deliberately left alone: the mu-plugins loader that runs before WordPress itself has booted, covered under point 24, can’t reach this shared function, the code it would need isn’t loaded yet at that point by design. Routing it through anyway would have meant a seventh independent copy of the same proxy-header logic, the exact pattern this fix exists to stop repeating.
Verified end to end against the real, shipped classes rather than a simulated stand-in, including the real Cloudflare-header resolution path, and checked in both directions: run against the old code first to confirm it actually fails the way described, then against the fix to confirm it doesn’t anymore.
26. A dashboard that reports in, but never phones home
Everything else on this list is about one site defending itself. This is about an agency running several, and it’s a genuinely different kind of feature from the other twenty-five, not an audit of existing code looking for a hidden defect, but something new, built against the same standard.
An agency managing multiple client sites wants one place to see them all at a glance. The obvious way to build that is a hosted service, agencies send us their sites’ data, we show it back to them on a dashboard we run. That’s also the one architecture this plugin’s entire “nothing about your visitors is ever sent anywhere” positioning couldn’t survive intact. So it isn’t built that way. A separate, free “SecondGate Hub” plugin exists instead, installed on infrastructure the agency already controls, not anything this project operates. Each client site pushes its own status there, hourly, on its own schedule, to a URL the agency configured themselves. Nothing is ever pulled. The dashboard never reaches out to a client site; a client site chooses to check in.
That still meant one genuinely attacker-adjacent surface needed the same scrutiny as everything else here: the Hub’s own endpoint, receiving pushes from sites it doesn’t control, authenticated by a key. A high-entropy key, generated with the same CSPRNG every other secret in this plugin uses, hashed the same way a device token is (fast hash, appropriate for a long random value nobody types by hand), shown to the agency exactly once and never retrievable again. Every incoming push checked against that hash with a timing-safe comparison, rejected identically whether the site ID or the key was wrong, no information leak about which part of a guess was closer.
As with any new component before it ships, this one went through WordPress.org’s own compliance tooling as a standard pre-launch step, the same discipline as everything else in this plugin. That pass caught the ordinary things a first build always turns up: readme metadata, translation-ready strings, table names passed through the query-preparation step as real parameters rather than built into the query string, a caching layer around every database read. Each fixed at the source. Table names now go through WordPress’s own identifier-placeholder syntax as genuine parameters. Every read is cached, with real invalidation on every write, confirmed by a test that adds, reads, deletes, and re-reads, watching the cache actually stay honest rather than assuming it does.
What sets this one apart from everything else on this list, though, isn’t the code, it’s how it got checked. Everything above has been proven in a sandbox: real classes, real fuzzing, real adversarial input, but a sandbox all the same. This is the first thing in this whole plugin that’s been watched working on two real, separate, live WordPress installations. A real key, generated by hand. A real hourly job, triggered manually rather than waited for. A real result, checked visually: a dashboard showing a live card with that site’s actual license state, actual threat score, actual blocked-visitor count, matched against the real site it came from. Then the connection was revoked, and the old key was confirmed, on a real subsequent attempt, to have genuinely stopped working, not assumed to have.
What this doesn’t mean
None of this claims the code is bug-free. We’d genuinely rather know if it isn’t. It means the reasoning behind these specific choices is available to check, in the actual source, not asserted in a changelog. If something above is wrong, or there’s a sharper way to have done any of it, that’s exactly the kind of thing worth hearing.
It also doesn’t mean every point above carries the same weight of evidence, and it’s worth being precise about which is which rather than letting the tone of one bleed into the others. Point 3 has actually been run against official conformance vectors, real browser-captured data, and 200,000 fuzzed adversarial inputs, checked, not just reasoned about. Points 21 through 25 were built the same way: exercised against simulated normal use and abuse before shipping, which caught and fixed a real bug in each along the way, not after. Point 26 goes one step further still, the only thing on this list confirmed on real, live infrastructure rather than a sandbox alone. Most of the rest of this list is an honest account of the reasoning behind a design decision, a real and useful thing to be able to show, but a more modest claim than “this has been independently tested,” and not one we’re going to blur into the other.






