=== Nolimit Security ===
Contributors: kristianwind
Tags: security, firewall, malware, two-factor, login
Requires at least: 5.8
Tested up to: 7.1.2
Requires PHP: 7.4
Stable tag: 1.4.8
WC requires at least: 6.0
WC tested up to: 11.1.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Firewall, login protection, country filtering, honeytraps, malware and vulnerability scanning, and an AI analyst — with no telemetry and no cloud account.

== Description ==

A complete security plugin for WordPress and WooCommerce that does not phone
home, does not require an account anywhere, and does not send your visitors'
addresses to a third party in order to protect you from them.

= The firewall =

Inspects every request before WordPress decides what to do with it, against
about sixty rules covering SQL injection, cross-site scripting, path traversal,
remote and local file inclusion, code execution, malicious uploads, the
WordPress-specific attacks, known scanning tools and malformed requests.

Each rule scores according to how little else a match could be, and a request
is refused when the total reaches the threshold for the sensitivity you chose —
so a single unmistakable attack is enough on its own, while two weak signals
have to agree.

* **Watching mode** records what it would have refused and refuses nothing. It
  is the honest way to switch a firewall on in front of a site that is already
  taking money.
* **Extended protection** writes one small file into `mu-plugins` so the
  firewall runs before any other plugin loads — before the code a request might
  be attacking. It removes itself when this plugin is deactivated.
* **Content rules are not applied to people who can edit posts.** The body of a
  request from an editor is a blog post, and a blog post about SQL injection
  contains SQL injection. That exemption is the single largest source of false
  positives in WordPress firewalls, and everything else still applies to those
  users.

= Login protection =

Attempt limiting where a lockout stops somebody logging in and nothing else —
the site stays readable, because a customer who has forgotten their password is
not an attacker. Only after three lockouts in a day does it become a real block.

Also: one error message whether the username or the password was wrong, an
invisible form field with a signed timestamp, username-probing blocked in all
three places WordPress leaks it, XML-RPC restricted or switched off, leaked
passwords refused via a check that never sends the password, and TOTP
two-factor authentication with recovery codes.

= Country filtering =

Block or allow by country, per area of the site — the whole site, the login
form, XML-RPC, the REST API, comments, registration, the WooCommerce checkout.
Verified search engines are never refused, and a visitor whose country is not
known is never refused either.

The country database is downloaded and compiled locally. No lookup leaves the
site.

= Traps =

A path that is forbidden in robots.txt and linked nowhere visible; an optional
decoy login form; invisible fields on the login, registration and comment
forms; and the thirty-odd paths that exist in no WordPress installation and are
asked for every day. A trap has no false positives to tune.

= The scanner =

* WordPress core files verified against wordpress.org's published checksums.
* Every file compared against what it looked like at the last scan, with the
  plugin and theme version taken into account — so an update is not reported as
  four hundred modified files.
* Malware signatures written to match the combination rather than the
  ingredient, so `base64_decode` on its own is not a finding.
* Published vulnerabilities for core, every plugin and every theme.
* A configuration audit, and an audit of the accounts that can do damage.

= The AI analyst =

Optional, off by default, and it works with whichever provider you have:
OpenAI, Anthropic, DeepSeek, OpenRouter, Groq, Mistral, Together, a model
running on your own server through Ollama or LM Studio, or anything else that
speaks the OpenAI API.

Five levels, from "answers when asked" through a daily briefing and proactive
triage to acting within a list of actions you wrote. Everything it does is
logged, reversible, and refused outright if it is not on that list.

What may be sent is six separate switches, and a button on the settings screen
renders the actual payload — not a description of it.

= Getting back in =

A security plugin is the one kind of plugin that can lock its owner out of the
site it protects. There is a line for wp-config.php that switches every
blocking decision off without needing the database, and a set of WP-CLI
commands that undo any of it from a shell.

== Privacy ==

**This plugin contains no telemetry.** It does not report anything about this
site to its author, and there is no account to create.

It makes exactly four kinds of outbound request, all of them to third parties
rather than to us, and every one can be switched off:

1. **The update feed** — a plain unauthenticated GET for the plugin's own
   release, with the WordPress user agent replaced so the site's address is not
   in it.
2. **Threat lists and the country database** — downloaded whole and compiled
   locally, specifically so that no visitor's address is ever sent anywhere. A
   reputation API would mean doing exactly that, on every request.
3. **Vulnerability lookups** — one plugin or theme slug per lookup, with no site
   address, no identifier and no version number. This is the site's inventory
   leaving the site, one name at a time, and it can be switched off on the
   Scanner screen.
4. **The AI analyst, when you switch it on** — to the provider you configured,
   with the key you supplied, containing what the six sharing switches allow.

The leaked-password check sends the first five characters of a SHA-1 hash of
the password and nothing else; the service answers with several hundred leaked
hashes beginning with those characters and the comparison happens here.

Passwords are never written to the log, including the ones submitted to the
decoy login form.

== Frequently Asked Questions ==

= I have locked myself out. =

Add this to wp-config.php, which you can reach over FTP, SSH or a hosting file
manager:

`define( 'NLSEC_DISABLE', true );`

Every blocking decision stops immediately and the admin screens keep working,
so you can find the setting that caused it. Nothing in the database can
override that line.

With shell access and WP-CLI: `wp nlsec panic`, or
`wp nlsec unblock 203.0.113.9 --allow`.

= Somebody has lost the phone with their second factor. =

`wp nlsec 2fa reset <user>` from a shell, or clear it from their profile as an
administrator. That lets them set a new one up — nobody can set one up on
somebody else's behalf, here or anywhere, because a second factor another
person enrolled is not a second factor.

= Does it work with WooCommerce? =

Yes, and it knows about it: the account login and registration forms, the
checkout as a filtering scope, and shop managers counted as accounts worth
protecting harder. It does not require WooCommerce.

= Will it slow the site down? =

The firewall reads one autoloaded option and, when the block list is empty,
touches no table at all. Rate limiting costs two small writes per request and
says so next to its switch. The country and threat-list lookups are binary
searches in a file, with no memory cost. The scanner runs from the scheduler,
in steps, against a time budget.

= The scanner found nothing. Is the site clean? =

It means nothing was found, which is not the same thing. Signature matching
finds what is known, and whatever got in yesterday is by definition not in
yesterday's list. The core checksums and the file-change baseline are the parts
that catch what signatures cannot.

== Changelog ==

= 1.4.8 =
Nolimit Security now tells WooCommerce that it works with High-Performance Order
Storage and the Cart and Checkout blocks, so it no longer appears under
"Incompatible with WooCommerce features" on the Plugins screen. Nothing else
changes. Tested against WordPress 7.1.2 and WooCommerce 11.1.2.

= 1.4.7 =
The scanner no longer reports Nolimit Security's own files, WooCommerce's files,
or built and minified scripts as malware, and a finding now closes by itself
once a full scan no longer sees it. The Overview chart keeps the busiest hour's
label inside the frame. The plugin now has a public page with screenshots and
setup instructions. Tested against WordPress 7.1.2 and WooCommerce 11.1.2.

= 1.4.6 =
Nolimit Security now offers three settings to the Setup screen in Nolimit
Assistant: hide the WordPress version, turn off pingbacks, and send browser
security headers. The first two can be undone in one click, and the third asks
for confirmation first. The recommendations appear in Assistant's Setup screen
from Assistant 0.3.0. Tested against WordPress 7.1.2 and WooCommerce 11.1.2.

= 1.4.5 =
"Ask about this site" no longer blames country filtering just because it is
switched on. It now says so only when the filter covers that kind of request and
can tell where a caller is, and then it names the country and the exact setting
to change. It also finds refusals in the log that it used to miss. Tested
against WordPress 7.1.2 and WooCommerce 11.1.2.

= 1.4.4 =
Other Nolimit plugins can now ask this one whether a form submission comes from
an address that has been misbehaving, and get an answer without using up the
submitter's rate limit. An address you have allowlisted is never reported as
suspicious. Tested against WordPress 7.1.2 and WooCommerce 11.1.2.

= 1.4.3 =
The status pill in the Nolimit dashboard cards is readable again. White text on
it measured 3.46:1 against this plugin's card colour, where small text needs
4.5:1, and six of the family's eight colours were below the line. It now darkens
its background instead of lightening it, which takes every colour to 7:1 or
better.

= 1.4.2 =
"Ask about this site" now links straight to the screen each finding is about,
instead of telling you what to change without saying where. Two of those links
pointed at the wrong screen and are corrected.

More importantly: asking about a mobile app never reported the REST restriction
or country filtering, so the answer could look complete while leaving out the
two things a WooCommerce or WordPress app most often fails on. Both are now
included.

= 1.4.1 =
Password managers no longer offer to fill the API key and webhook fields.
`autocomplete="off"` was never enough — managers ignore it and match on the
field being a password field with "key" in the label. If one has already filled
a credential field here, the sign is that a value is stored and the service
answers 401: clear it and paste the real key.

= 1.4.0 =
The API key, the notification webhook and the Wordfence key are no longer kept
in the settings row WordPress loads on every page view. They move to their own
row on upgrade, read only when something actually needs them — which on a
front-end request is never. Nothing to do; existing keys are carried across.

Also centres the infinity sign in the Nolimit menu, including when the menu is
collapsed.

= 1.3.1 =
Fixes the AI analyst answering "is this plugin blocking <service>?" without the
measurement it was supposed to be reading. The plugin measured whether it was
interfering, showed you the result, and then asked the model the question
without it. The findings on screen were always correct; the paragraph
underneath them was written from general context.

= 1.3.0 =
The site health audit now tells the difference between updates nobody has
applied and updates that cannot be applied. If the same updates have been
offered unchanged for over a week, the advice stops being "go to Dashboard →
Updates" and starts naming the reasons the button does not work.

= 1.2.2 =
Fixes a bug where saving one settings tab could switch off protections on
other tabs, including the firewall itself. Two settings that no screen could
switch on are restored automatically; anyone on an earlier version should
check their firewall settings.

= 1.2.1 =
Fixes every subpage refusing to open with "Sorry, you are not allowed to
access this page". Introduced in 1.1.0.

= 1.2.0 =
The Nolimit menu carries an infinity sign instead of a cog.

= 1.1.2 =
Fixes the admin tables on a phone, where an IP address could wrap to one digit
per line, and removes six places that printed a time half in Danish and half in
English on a non-English site.

= 1.1.1 =
Fixes the menu entry, which read "Overview" instead of "Security" after 1.1.0
moved the sections into tabs.

= 1.1.0 =
One entry in the Nolimit menu instead of seven. Firewall, Login, Scanner,
Activity, AI analyst and Settings are tabs on the Security page. Every existing
link still works.

= 1.0.3 =
Fixes the admin screens in dark mode, where most text was inheriting a colour
meant for a white page.

= 1.0.2 =
Adds `wp nlsec why <reference>`, so the reference printed on the block page can
be looked up.

= 1.0.1 =
Fixes a fatal error on the plugin's own admin page.

= 1.0.0 =
First release.
