=== secaudit – Security Audit & Compliance ===
Contributors: secaudit
Tags: security, malware, audit, compliance, gdpr
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.31.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Malware, vulnerability and file-integrity scanning with a GDPR, NIS2 and OWASP compliance summary, and free vulnerability data. Audit your own site.

== Description ==

**Security Audit & Compliance** audits your WordPress site for malware, vulnerabilities, and configuration weaknesses — and maps every finding to the EU compliance frameworks that matter: **GDPR, NIS2 and CRA**. It is a defensive auditing tool: it detects and reports, it does not attack.

Made by [secaudit.eu](https://secaudit.eu/). The vulnerability database, malware signatures, virtual patches and the public verification page come free from secaudit.eu, with no account and no API key.

Unlike firewall-first plugins, this tool focuses on two things competitors are weak on: **deep code-level vulnerability analysis** and **EU compliance reporting**. Use it only on sites you own or manage.

= What it scans =

* **Code & secrets** — risky PHP functions (token-based, low false positives), hard-coded API keys / passwords, exposed sensitive files (`.sql`, `wp-config.php.bak`, `.env`).
* **File integrity** — baseline hashing, detection of added / changed / deleted files, and high-entropy (encrypted / obfuscated) content. For your own files (wp-config.php, .htaccess, mu-plugins, cache drop-ins, plugins and themes not from WordPress.org) the baseline keeps a copy, so each change explains itself: who made it (the dashboard editor, a caching plugin, WordPress, or something outside WordPress), when, which lines changed (secrets masked) and whether the added code is dangerous.
* **Malware signatures** — webshell and obfuscation patterns plus SHA-256 hash matching, updatable from a signature feed. Optional ClamAV on servers that have it.
* **Hacklinks / SEO spam** — hidden links (display:none, off-screen, opacity), pharma/casino spam, injected external-link clusters, siteurl tampering.
* **Compromise indicators** — hidden admin accounts, database spam injection (options, every public content type, reusable blocks and templates, approved comments), suspicious cron jobs, WordPress core checksum verification, and program code hidden in translation files (`*.l10n.php`).
* **Code vulnerabilities (taint analysis)** — SQLi, XSS, LFI/RFI, path traversal, command injection, object injection, SSRF, open redirect, privilege escalation, missing-nonce CSRF (also links that delete or change data), REST routes without a permission check, logged-out actions that change data, actions guarded only by a capability every member has, settings changed from user input, meta fields open to every editor, unchecked file uploads and unsafe CORS, across installed plugins and themes.
* **Plugin files** — every file of plugins from WordPress.org is compared with the official version, using the checksums WordPress.org publishes; changed files and PHP files that do not belong are listed.
* **Installed plugin / theme / core** — outdated versions via the free WordPress.org data; known vulnerabilities (CVE) matched on your own site against the free, signed secaudit.eu database built from the official CVE List (your plugin list is never sent); PHP end-of-life and known vulnerabilities in PHP itself (such as PHP2Shell, CVE-2026-109618 and CVE-2026-109619), a signal for plugins and themes that are not from WordPress.org (possibly "nulled"), and a CVSS-based risk score.
* **Authentication** — weak/common password audit (hash dictionary test, your own site), default `admin` account, password policy enforcement.
* **Configuration** — HTTPS, debug output, risky default role for new users, XML-RPC, user names exposed through the REST API, security headers, directory listing, whether the dashboard file editors are on, sign-ins kept open for more than 30 days, sign-in cookies without the Secure flag on an HTTPS site (common behind a proxy or CDN), administrators signed in on many devices, WPGraphQL settings (debug mode, public schema, public tracing, no query depth limit), default security keys, the default `wp_` table prefix, folders and files anyone on the server can change, phpinfo pages, `.git` / `.svn` folders in the web root, the WordPress version in the page source and files loaded over plain HTTP on an HTTPS page.
* **Virtual patching** — blocks attacks on a known vulnerability in one of your plugins or themes until you can update, using the same free vulnerability database. A rule is created only for an installed, active, affected version, checks only requests that reach that plugin, and disappears after you update. Off by default. The general firewall is separate (see Firewall).
* **Two-factor authentication** — TOTP for any user (Google Authenticator, Authy, 1Password and others) or a code sent by email, with one-time backup codes. Optionally, "Trust this browser for 30 days" on the code page (forgotten again when the password changes).
* **Privacy** — security-log retention and IP shortening, suggested privacy-policy text, and WordPress's personal-data export and erase tools.

= Fixing what it finds =

* **Replace with the original** — a changed WordPress core file, or a changed file of a plugin from WordPress.org, is replaced with the official original. The download is checked against the official checksum first; if it does not match, nothing is written. The previous copy is kept in quarantine.
* **Quarantine** — suspicious files (for example a PHP file in uploads or a file that does not belong to a plugin) are moved to a locked folder with a random name, where they can no longer run. Put a file back if the site needs it, or delete it for good. Official WordPress files and wp-config.php are never moved.
* **This change is mine** — a changed, added or deleted file reported by the integrity scan can be accepted into the baseline one file at a time (the deleted one forgotten), with the diff shown first; written to the event log.
* **False alarms** — mark a finding as a false alarm and it no longer counts toward the score, the seal or the lists. Each mark is written to the event log, and the compliance report lists marked findings separately. You can count a finding again at any time.
* **Automatic security updates** (opt-in) — when an update fixes a known vulnerability in an installed plugin or theme, WordPress installs it automatically. Other updates are left to you.

Every change asks for a second press first and is written to the event log.

= Login and account protection =

* **Robot check** — the login, registration and password-reset forms let the visitor's browser solve a small proof-of-work puzzle first (about half a second on a phone). Bots that send thousands of attempts pay that cost every time. It runs on your site: no Google reCAPTCHA or other outside service. Strict mode also refuses logins without it. Optionally also on the comment form (works with page caching), with at most 5 comments per address in 10 minutes.
* **Custom login address** — move the login page from wp-login.php to an address only you know; visitors who are not logged in get "page not found" at wp-login.php and wp-admin. Lost it? Add `define( 'WPSA_DISABLE_LOGIN_URL', true );` to wp-config.php, or run `wp secaudit login-url off`. On a multisite network it works on every site, after each site's own address.
* **Hide user names** — closes the usual ways to collect user names: `?author=1` addresses, the REST API user list, author names in embeds and the sitemap, and login and password-reset messages that reveal whether an account exists.
* **Sessions** — set the longest time a sign-in lasts (also shortens sign-ins already open, and those other plugins keep open for months), how many sign-ins an account may have at once (the oldest ends when someone completes a sign-in beyond the limit), and sign-out after 15 minutes to 4 hours without activity (WordPress's background checks in an open tab do not count, typing in the editor does; an open editor shows WordPress's sign-in window, so nothing typed is lost). Applies to accounts that can write or manage content, not to customers or subscribers.
* **Sign-in alerts** (opt-in) — when an administrator (or anyone who can write content) signs in from a browser or device the account has not used before, the owner gets an email with the time, address and browser, after the whole sign-in including two-factor authentication. Sign-ins through XML-RPC are checked too. Administrator alerts also go to the site's admin email.
* **New passwords on request** — after a break-in, sign out everyone with the chosen roles and have them set a new password through an emailed link at their next sign-in; their application passwords are deleted too, and optionally their passkeys. An optional password age limit (90, 180 or 365 days) does the same when a password gets too old, for policies that require it. `wp secaudit password-renew cancel` undoes the request.
* **Application passwords** — keep WordPress's default, allow them for administrators only, or turn them off.
* **Sign-in link by email** (opt-in) — the login page offers "Email me a sign-in link instead": a link that works once, within 15 minutes, signs the person in without a password. Choose everyone except administrators and editors (anyone who can change settings or users or add code, on any site of a network), or everyone. It follows WordPress's password-reset rules, so accounts waiting for approval or marked as spam get no link. Opening the link shows the account name and a "Sign in" button first, so email scanners do not use it up, with a warning when it was asked for in another browser; the page gives the same answer whether or not an account exists; two-factor authentication still asks for the code; paused in safe mode. It also lets people back in after a login lockout.
* **Breached password check** (opt-in) — rejects new passwords that appear in data breaches, using the Pwned Passwords service of Have I Been Pwned with k-anonymity: only the first 5 characters of the password's SHA-1 fingerprint are sent.
* **Required two-step login** — choose the roles that must use it; people without it (or a passkey) can only open their profile until they set it up.
* **Accounts that can change the site** — administrators, editors and shop managers with their last sign-in and flags for new, never-used and long-unused accounts. **Suspend** an account (it keeps its content but cannot sign in by any route, and its sessions end); optionally suspend content accounts automatically after 90, 180 or 365 days without signing in (never administrators). **Temporary access** — give an existing user administrator or editor rights for 1 hour to 7 days, or create a temporary account for a developer; access ends by itself.
* **Hide the WordPress version** — removes the generator tag that tells bots which version you run.
* Also: two-factor authentication (above), and login throttling with temporary IP locks (below).

= Firewall =

* **Firewall** (off by default) — checks every request for known attack patterns: SQL injection, cross-site scripting, file access, code and object injection, attack tools and probes for secret files. Start with "Log only" to see what it would block, then switch to blocking. Administrators and editors are never checked, passwords are never read, comment and message fields are only checked for PHP code, and any check can be switched off for one form field with "Allow". An address that attacks 10 times in 10 minutes is blocked for an hour. Besides its built-in rules it uses extra rules from secaudit.eu, signed and checked before use, and it starts before other plugins load (from a small file it writes to wp-content/mu-plugins) so it also covers code that plugins run while loading. Developers can add rules with the `wpsa_firewall_match` filter.
* **Page probing lock** (off by default) — an address that asks for many pages that do not exist within 5 minutes (15, 30 or 60) is blocked for 30 minutes; IPv6 addresses are counted per /64 network. Images, styles, scripts, favicon and robots.txt are not counted; logged-in users, addresses an administrator has used and the crawlers of Google, Bing, Yandex, Baidu, Apple and Yahoo are not locked.
* **Fake crawler block** (off by default) — a request that claims to be Google, Bing, Yandex, Baidu, Apple or Yahoo is checked against that search engine's own DNS records and blocked if the address does not belong to it. If the check cannot be done, nothing is blocked. Both protections stay off when visitor addresses cannot be read (a proxy server WordPress does not see through).
* **Request limit** (off by default) — an address that asks for more than 60, 120 or 240 pages in a minute gets "429 too many requests" for 10 minutes. Logged-in users, your "Never block" list, addresses an administrator has used and real search engine crawlers are never paused; cached pages are not counted.
* **Address lists** — addresses or ranges that are never blocked (your office) or always blocked, and the addresses locked right now (failed logins, firewall bans, page probing, request limit) with an Unlock button.
* **Safe mode** — if a protection ever locks you out, add `define( 'WPSA_SAFE_MODE', true );` to wp-config.php: everything that can block a visitor or an administrator pauses until you remove the line.

= Country blocking =

* **Country blocking** (off by default) — block chosen countries, or allow only chosen countries, on the login page, registration, password reset and XML-RPC (recommended) or on the whole site. The country table (IP Geolocation by [DB-IP](https://db-ip.com), CC BY 4.0) is downloaded from secaudit.eu and looked up on your own site, so visitor addresses are never sent anywhere. Addresses with an unknown country and logged-in administrators are never blocked, and the plugin refuses a rule that would block your own country.

= Shared attacker list =

* **Shared attacker list** (opt-in) — sites that take part tell secaudit.eu which addresses attacked them (only the IP address, the kind of attack and the time), and block addresses that attacked at least five different sites in the last seven days, on the login page (recommended) or on the whole site. secaudit.eu stores a pseudonym of your domain, deletes reports after seven days and gives the list only to sites that take part. Leaving deletes your site and its reports on secaudit.eu.

= Hardening in one click =

* **Security headers** — X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy and HSTS, each switched on separately, plus a redirect from http:// to https:// that checks for redirect loops before it stays on.

= Server health =

* **Is the server big enough?** — the plugin measures how long the server needs to build the pages visitors open, how much PHP memory they use, and every page that stopped because PHP ran out of memory or time. Together with PHP version, OPcache, object cache, free disk space, late scheduled tasks and the settings loaded on every page, it tells you whether the server keeps up, which settings to change (usually free), or that the server is too small, with the resources it should have (processor cores, memory, PHP, caching, disk). It names resources, never hosting companies. Pages are sampled, so it adds almost no load; the measurements stay on your site and are never sent anywhere. It can be switched off on the dashboard, which also deletes the measurements. With downtime alerts on, the card also shows the outages secaudit.eu saw from outside in the last 30 days.
* **Low disk alert** — an email (and a chat alert, when set up) when less than 1 GB or 10 percent of the disk is free, at most once a day.

= Optional services from secaudit.eu =

All of these are off until you turn them on, and each says exactly what it sends (see External services below).

* **Downtime and certificate alerts** — secaudit.eu opens your home page every 5 minutes and reads its certificate once a day, and e-mails you when the site cannot be reached, when it is back and when the certificate is about to expire. A site that is down cannot report its own outage; this is why the check runs outside. Alerts start after you confirm the address.
* **Agency panel** — link the sites you look after to one account on secaudit.eu (sign-in by e-mailed link or passkey) and see each site's score, findings, waiting updates and versions in one place. Only a short summary and the titles of the latest findings are sent (no file paths or personal data).
* **Off-site scanning** — the heavy part of the malware scan (hundreds of patterns per file) runs on secaudit.eu instead of your server. Only code files your site cannot verify itself are sent, without file names or paths, and they are scanned in memory and not kept.
* **AI assistance** — "Explain" tells you in plain words what a finding means and what to do; "Second opinion" says whether a code finding looks real or like a false alarm; AI can draft the 24-hour and 72-hour incident notifications and explain the Server health measurements. Secrets are removed before anything is sent, answers are advice only, and they never change a finding or the score.
* **Signed compliance reports** — the NIS2 Article 21, GDPR Article 32 and OWASP Top 10 report as a PDF or CSV document, laid out and signed by secaudit.eu with a report code (see External services for what is sent). During early access every site can use them at no charge; secaudit.eu announces the end of early access at least 14 days ahead, and after that they are part of secaudit.eu Pro.

= Reports by email and the command line =

* **Weekly summary email** (opt-in) — every Monday: the score and how it changed, open findings, what to fix first, the attacks blocked that week and one line on server health.
* **WP-CLI** — `wp secaudit status`, `scan`, `findings`, `ignore`, `repair`, `quarantine`, `login-url`, `disable`, `twofa-off`, `password-renew`, `update-signatures`, `update-vulndb`, `send-summary` and `report` (a signed report from secaudit.eu, Pro).
* **Event log download** — the whole event log as a CSV file (opens in Excel and LibreOffice; cells that would run as formulas are neutralised).
* **Live traffic** (opt-in) — every request that reaches WordPress, refreshed every 5 seconds on the Event Log page: address, page, answer code, and whether it is a visitor, a logged-in user, a search engine, another bot or a blocked attack. Filter it, and block an address from the list with two clicks (not your own, the server's, a local or shortened address, or one an administrator used recently). Kept for 24 hours on your site only; values typed into addresses (search words, reset keys, email addresses) and the site a visitor came from are never recorded.
* **Chat alerts** (opt-in) — important events (a new administrator, two-step login turned off, a sign-in from a new device, a plugin installed, changed files, new serious scan findings, low disk space) also go to Slack, Discord, Google Chat or any address that accepts JSON. At most 30 messages an hour.

= Compliance reporting =

Every finding is mapped to **OWASP Top 10:2025**, **GDPR Article 32**, and **NIS2 Article 21** measure areas. The **compliance summary** on Compliance & Reports shows, for each framework, every measure or category with its status (good, needs attention, problem, or your own task such as backups and training) and the number of open findings: all ten NIS2 Article 21 minimum measures, the GDPR Article 32 measures in place and the findings by area, and the ten OWASP categories. All open findings are listed on the Scans page. Signed report documents (PDF or CSV, with a report code) are made by secaudit.eu as a Pro service; the incident-notification draft (24h / 72h / 1 month) stays on your site. The event log records failed logins, new and deleted users, role changes, plugins and themes installed, updated, activated or deleted, WordPress updates, file changes and every setting this plugin changes.

= Abuse & rate limiting =

Application-layer measures that reduce abuse: login throttling with temporary IP lock, XML-RPC disabling, anonymous REST rate limiting, and traffic-spike alerts. **This is not DDoS protection** — a plugin cannot stop a volumetric DDoS; for that, put a CDN/edge firewall such as Cloudflare in front of your site. The plugin tells you this plainly.

= Trust badge =

When your site scores well, display an honest, time-stamped trust badge (Gold / Silver / Bronze tiers) in your footer, linking to a verification page. The badge shows your real score and scan date — never a false "guaranteed secure" claim — and stays hidden below a minimum score.

= Languages =

English (default), German, French, Spanish, Portuguese, Italian, Polish, Turkish.

== Installation ==

1. Upload the `security-audit-compliance` folder to `/wp-content/plugins/`, or install the ZIP via Plugins → Add New → Upload.
2. Activate the plugin through the Plugins menu in WordPress.
3. Open **Security Audit** in the admin menu and click **Start the first full scan**.
4. Optional: click **Turn on vulnerability checks** on the dashboard, and open **Security Audit → Settings** to turn on daily signature updates.

== Frequently Asked Questions ==

= I changed the login address and lost it. How do I log in? =
Add `define( 'WPSA_DISABLE_LOGIN_URL', true );` to wp-config.php; the login page is wp-login.php again. With WP-CLI: `wp secaudit login-url off`. The address was also emailed to the site administrator when you set it.

= A protection locked me out. How do I get back in? =
Add `define( 'WPSA_SAFE_MODE', true );` to wp-config.php. It pauses everything that can block a visitor or an administrator (firewall, virtual patches, country blocking, shared attacker list, robot check, custom login address, login lockouts, two-step login, the lock on plugin changes, security headers) until you remove the line; every admin page shows a warning while it is on. With WP-CLI you can turn off one protection instead: `wp secaudit disable firewall` (or `countries`, `shared-list`, `robot-check`, `login-url`, `plugin-lock`, `all`), and `wp secaudit twofa-off USER` after a lost phone.

= Does it work on a multisite network? =
Yes. Activate it for the whole network (Network Admin → Plugins → Network Activate, WordPress 6.1 or newer). The network is then managed from the main site: its settings and protections apply to every site, files are scanned once, and the database scans check every site (up to 200) and name the site in each finding. Only network administrators see the panel, because repairs and quarantine change files of the whole network. The custom login address works on every site of the network, after each site's own address. Network Admin → secaudit lists the sites. If you activate it on single sites instead, each site keeps its own settings, but the panel is still for network administrators only.

= Does this plugin attack other sites? =
No. It is a defensive tool: it checks the site it is installed on and never sends requests to attack or test other sites. Use it only on sites you own or manage.

= Does it stop DDoS attacks? =
No. No WordPress plugin can stop a real (volumetric) DDoS, because the traffic is handled before PHP runs. The abuse & rate-limiting features reduce application-layer abuse; for real DDoS protection use a CDN/edge firewall such as Cloudflare.

= Do I need an API key? =
No. Everything that runs on your site is free. Version/update checks use the free WordPress.org data. Known vulnerabilities come from the free secaudit.eu vulnerability database, which is built every night from the official CVE List (CVE Program). Malware signature updates come from the free, signed secaudit.eu feed.

= Does the vulnerability check send my plugin list anywhere? =

No. The database is split into 256 parts by a fingerprint (hash) of each plugin's name. Your site downloads only the signed parts that cover its own plugins and themes and compares versions on your server. Each part covers hundreds of plugins, so the download does not reveal which plugins you use. The check is off until you turn it on.

= Will it slow down my site? =
Scans run on demand or on a schedule and are split into time-budgeted batches so large sites do not time out. The badge and runtime protections are lightweight.

= Is the trust badge honest? =
Yes. It shows your real score and last scan date, drops its tier or hides entirely if the score falls or the scan is stale, and never claims "guaranteed secure." It uses no official EU emblem or CE mark.

= I lost the phone with my authenticator app. How do I log in? =
Use one of the backup codes you saved when you turned on two-factor authentication; each works once. If you have none left, another administrator can turn two-factor authentication off on your profile page (Users → your user). With WP-CLI: `wp user meta delete <user> wpsa_2fa_enabled`.

= What is removed when I delete the plugin? =
Deactivating keeps everything. Deleting the plugin removes all its data: settings, scan results, the security event log and users' two-factor keys. If you need the security log for your records, take a database backup before deleting.

== Screenshots ==

1. Security Dashboard: the security score, the next step to take, open findings by severity, blocked attacks, changed files and the trust seal.
2. Scans: every open finding in one to-do list, most serious first, each with its OWASP Top 10 category and its own actions.
3. File changes explained: who changed a file, when and which lines, shown line by line, with quarantine or "This change is mine" one press away.
4. Compliance & Reports: NIS2 Art. 21, OWASP Top 10:2025 and GDPR Art. 32 summaries with the record of the latest signed report.
5. Protection: failed logins and locked IP addresses in the last 24 hours, and the state of the login limit, two-factor authentication, REST API limit, virtual patches and firewall.
6. Trust badge and public verification page: badge style and placement with a live preview of the "Security & Compliance Check" plaque, and the opt-in verification page on secaudit.eu.
7. Hacked Site Cleanup: the recovery steps in order, here an unknown PHP file in uploads to move to quarantine and the administrator accounts to check.

== External services ==

The plugin works locally. It contacts the services below only for the features listed, and only after you start a scan, press a button or switch the feature on. Server health measurements are never sent anywhere.

= WordPress.org API (api.wordpress.org) =
Used by the update check, the WordPress security-status check (`/core/stable-check/`, nothing is sent) and the core-file check when you run a scan (or a scheduled scan you enabled). Sent: the slugs of your installed plugins and themes; the core-file check uses WordPress's own checksum request, which includes your WordPress version and site address like every WordPress update check. Requests made by this plugin carry a generic user agent without your site address. WordPress itself uses the same service. [Privacy policy](https://wordpress.org/about/privacy/)

= WordPress.org files (downloads.wordpress.org, core.svn.wordpress.org, plugins.svn.wordpress.org) =
Used when you run the **Plugin files** check, during the file scan (files that match the official copy are not searched for risky code, so library code does not raise false alarms), and when you press **Replace with the original**. The plugin downloads the official checksum list of each installed plugin from WordPress.org (`downloads.wordpress.org/plugin-checksums/<plugin>/<version>.json`) and, for a repair, the original file from the official WordPress or plugin repository. Sent: the plugin name and version, or the WordPress version and file name, as part of the address, with a generic user agent without your site address. [Privacy policy](https://wordpress.org/about/privacy/)

= secaudit.eu country table (opt-in) =
Only if you turn on country blocking. The plugin downloads a signed index from `https://secaudit.eu/geo/index.json` (with `index.json.sig`) and two table files listed in it, at most once a day, and stores them in `wp-content/uploads/wpsa-geo/`. The index is checked against the built-in Ed25519 public key and each table against its SHA-256 hash. Visitor addresses are looked up on your site and never sent. Sent: nothing except normal HTTPS requests with a generic user agent. The country data is "IP to Country Lite" by DB-IP (https://db-ip.com), licensed under CC BY 4.0. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu shared attacker list (opt-in) =
Only if you join the shared list (Protection → Shared attacker list). Joining sends your domain, WordPress path and plugin version to `https://secaudit.eu/api/shared/join`, and secaudit.eu calls back a REST endpoint on your site with a one-time token to confirm the domain (as for the verification page); secaudit.eu then stores only a keyed hash of the domain. Once an hour, the plugin sends the IP addresses of attacks it blocked, with the kind of attack and the time, to `https://secaudit.eu/api/shared/report`, and downloads the signed list from `https://secaudit.eu/api/shared/list`. No page addresses, request contents, user names or data of other visitors are sent. secaudit.eu deletes reports after seven days; leaving deletes the site and its reports. Your own privacy policy should mention this; WordPress shows suggested text under Settings → Privacy. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= Have I Been Pwned — Pwned Passwords (api.pwnedpasswords.com) =
Only if you turn on the breached password check (Identity & Access). When someone sets or resets a password, the plugin sends the first 5 characters of the password's SHA-1 fingerprint to `https://api.pwnedpasswords.com/range/<5 characters>` and compares the answer on your server. The password itself and its full fingerprint never leave your site; the request asks for a padded answer, so even its size does not reveal anything. Requests carry a generic user agent without your site address. If the service cannot be reached, the password is accepted. Provided by Have I Been Pwned. [Terms of use](https://haveibeenpwned.com/TermsOfUse), [Privacy policy](https://haveibeenpwned.com/Privacy)

= secaudit.eu firewall rules (only while the firewall is on) =
While the firewall is on, the plugin downloads extra firewall rules (data only, never code) from `https://secaudit.eu/feed/firewall.json` (with `firewall.json.sig`) once a day and when you press **Update rules now**. The download is checked against the built-in Ed25519 public key and ignored if the signature does not match. Sent: nothing except a normal HTTPS request with a generic user agent, without your site address. When early access ends (secaudit.eu announces the end at least 14 days ahead), new rules reach this public file 30 days after they are released. With an active secaudit.eu Pro license the plugin first asks `https://secaudit.eu/api/feed/firewall.json` (and `.sig`), which has them the same day; that request carries the license key and your site address (domain and WordPress path) in the headers `X-SA-License` and `X-SA-Site`, which secaudit.eu uses only to check the plan and does not store beyond its usual request counter (deleted after 48 hours). The key is sent nowhere else. If that request fails for any reason, the public file is used. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu signature feed =
Downloads malware detection signatures (data only, never code) from `https://secaudit.eu/feed/signatures.json` when you press **Update Signatures**, and once a day if you turn on automatic updates. The download is checked against a built-in Ed25519 public key and ignored if the signature does not match. Sent: nothing except a normal HTTPS request with a generic user agent (`SecurityAuditCompliance/<version>`, without your site address); like every web server, secaudit.eu sees the IP address of your server. When early access ends (secaudit.eu announces the end at least 14 days ahead), new signatures reach this public file 30 days after they are released. With an active secaudit.eu Pro license the plugin first asks `https://secaudit.eu/api/feed/signatures.json` (and `.sig`), which has them the same day; that request carries the license key and your site address (domain and WordPress path) in the headers `X-SA-License` and `X-SA-Site`, which secaudit.eu uses only to check the plan and does not store beyond its usual request counter (deleted after 48 hours). The key is sent nowhere else. If that request fails for any reason, the public file is used. The same key checks both files. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu public verification page (opt-in) =
Only if you switch on the public listing. Sent after each scan and once a day while the listing is on: your domain, the trust score, the scan date and pass/fail results for malware, core files and spam links, whether this plugin's own files are the original release (ok, modified or unknown; see the plugin file list below), plus the optional business details you enter. Detailed findings never leave your site. To prove domain control, secaudit.eu calls back a REST endpoint on your site with a one-time token. Turning the listing off removes the page. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu report documents (only when an administrator asks for one) =
Only when an administrator presses **Download signed report (PDF)** or **(CSV)** on Compliance & Reports, or runs `wp secaudit report`. Nothing is sent in the background. The site sends the report's content to `https://secaudit.eu/api/reports/render` so that secaudit.eu can lay it out and sign it: the site's address (domain and WordPress path), the WordPress, PHP and plugin versions, the score, for each finding its title, severity and the affected plugin, theme, component or file path, and the status of each GDPR, NIS2 and OWASP measure. Not sent: user names (where a finding concerns a user account, the name is shortened, for example ad***), passwords, file contents or visitor data. To make sure the request comes from this site, secaudit.eu calls the site back once (a REST endpoint with a one-time token, as for the verification page). secaudit.eu keeps the content only while making the file and then deletes it. It keeps a record of the document for 3 years: the report code, the site address, the report type and format, the date, the plan and a SHA-256 fingerprint of the file, so that a report code can be checked later. During early access every site gets report documents at no charge (the record says plan "free"); secaudit.eu announces the end of early access at least 14 days ahead, and after that they are part of secaudit.eu Pro. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu plugin file list (self-check) =
Once a day (together with the daily tasks the plugin already runs) and after the plugin is updated, the plugin downloads the signed list of the files of its own version from `https://secaudit.eu/feed/plugin/<version>.json` (with `.json.sig`), checks it against the built-in Ed25519 public key and compares the SHA-256 fingerprints of its own files with it on your site. If a file was changed or removed, or a code file was added to the plugin folder, the plugin's pages say so. This download sends no information about your site: only a normal HTTPS request with a generic user agent; the version is part of the address, and like every web server secaudit.eu sees the IP address of your server and does not store it. Developers can turn the check off with `add_filter( 'wpsa_self_check', '__return_false' );`. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu vulnerability database (opt-in) =
This also powers virtual patching. Virtual patching runs entirely on your site from the already-downloaded data and sends nothing extra.

Only if you turn on vulnerability checks. During scans (at most twice a day) the plugin downloads a signed index from `https://secaudit.eu/vulndb/index.json` and, using it, only the parts of the database that cover your own plugins and themes (`https://secaudit.eu/vulndb/b/…`). Versions are compared on your server. The index is checked against the built-in Ed25519 public key, each part against the SHA-256 hash in the index; anything that does not match is ignored. Sent: nothing except normal HTTPS requests with a generic user agent (`SecurityAuditCompliance/<version>`, without your site address); the names of your plugins and themes are never sent, and secaudit.eu sees the IP address of your server like any web server. The data comes from the CVE List of the CVE Program (© The MITRE Corporation, used under the CVE Program Terms of Use). Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu AI assistance (opt-in) =
Only if you turn on AI assistance (Settings → AI assistance), and then only when an administrator presses **Explain**, **Second opinion**, one of the incident-notification buttons or **Ask the AI for advice** on the Server health card, or runs `wp secaudit ai explain`. Turning it on sends your domain, WordPress path and plugin version to `https://secaudit.eu/api/ai/join`, and secaudit.eu calls back a REST endpoint on your site with a one-time token to confirm the domain (as for the verification page). Each request goes to `https://secaudit.eu/api/ai/ask` (the answer is collected from `https://secaudit.eu/api/ai/result`), together with the language of the dashboard. Sent for **Explain**: the type, level, OWASP category, text and location of that one finding; user names and display names of the finding and of the administrators are replaced by `[user]`, e-mail addresses by `[email]`, and server folders are shortened. Sent for **Second opinion**, in addition: at most 8 KB of the reported file around the reported line, after passwords, keys, tokens, connection strings and similar secrets are removed; you see exactly this excerpt before each request. `wp-config.php`, copies of it, `.env` files, database dumps, logs, backups and key files are never sent. Sent for the incident notification: the number of findings per level, their types, the date the clean-up started and the clean-up steps marked as done (not the site name or domain: the draft says `[site name]` and your site fills it in). Sent for server advice: the server health measurements of the last 7 days as numbers (page build times, PHP memory use and limit, pages stopped by the memory or time limit, server load, PHP version, OPcache and object cache on or off, free disk, late scheduled tasks, size of autoloaded settings) and the names of the problems found; no site name, domain or content. secaudit.eu passes the request to an AI model (Claude by Anthropic, or OpenAI if Claude cannot be reached) and does not keep the file excerpt after delivering the answer. Answers are advice only: they never change a finding, the score, the seal or a file. Answers are kept on your site for 30 days (older ones are deleted) so they are not requested again. Turning it off calls `https://secaudit.eu/api/ai/leave`, which deletes the site on secaudit.eu, and then deletes the answers kept on your site; if secaudit.eu cannot be reached, AI assistance stays on until it can be turned off. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/); the AI providers: [Anthropic privacy policy](https://www.anthropic.com/legal/privacy), [OpenAI privacy policy](https://openai.com/policies/privacy-policy/)

= DNS lookups for the fake crawler block and the page probing lock (opt-in) =
Only if you turn on "Block fake search engine crawlers", the page probing lock or the request limit (Protection → Bots and page probing), and only for requests that claim to be a search engine crawler. The plugin asks your server's DNS resolver for the name of the visitor's address (reverse lookup) and then for the addresses of that name (forward lookup), as Google, Bing and the other search engines describe for verifying their crawlers. The page probing lock does the same lookup, only for such a request and only when the address has reached the limit, so that a real crawler is not locked. These lookups go through your server's normal DNS, like any other name lookup; no other service is contacted. Each address is looked up at most once a day, and only one lookup runs at a time.

= Your chat service — Slack, Discord, Google Chat or a webhook you choose (opt-in) =
Only if you enter an incoming webhook address (Settings → Alerts in Slack, Discord or another chat). Then, for important security events, the plugin sends a short message to that address: the site name and address, the kind of event, the event text as written in the event log (it can contain a user name, never an IP address) and a link to the event log. At most 30 messages an hour. Pressing **Send a test message** sends a test message. Nothing goes to secaudit.eu. The service is the one you chose; its terms and privacy policy apply: Slack https://slack.com/terms-of-service and https://slack.com/privacy-policy, Discord https://discord.com/terms and https://discord.com/privacy, Google Chat https://policies.google.com/terms and https://policies.google.com/privacy. Delete the address (**Stop sending**) to stop.

= secaudit.eu downtime and certificate alerts (opt-in) =
Only if you turn on the alerts (Protection → Downtime and certificate alerts). Turning them on sends your domain, WordPress path, plugin version, the alert e-mail address and the dashboard language to `https://secaudit.eu/api/monitor/join`; secaudit.eu confirms that the site belongs to the domain by calling back a REST endpoint on your site with a one-time token (as for the verification page), and sends a confirmation link to the e-mail address. Alerts start only after that link is opened. secaudit.eu then opens your home page every 5 minutes and reads its certificate once a day, and writes to the address when the site cannot be reached, when it is back and when the certificate is about to expire. The plugin asks `https://secaudit.eu/api/monitor/status` for the current state when you open the card, and sends a new address to `https://secaudit.eu/api/monitor/email` when you change it. Turning the alerts off calls `https://secaudit.eu/api/monitor/leave`, which deletes the site and the address on secaudit.eu; every alert e-mail has a link that does the same. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu agency panel (opt-in) =
Only if you link the site to an agency panel account (Settings → Agency panel) with the one-time code shown in that account. Linking sends your domain, WordPress path, plugin version and the code to `https://secaudit.eu/api/agency/link`; secaudit.eu confirms that the site belongs to the domain by calling back a REST endpoint on your site with a one-time token. After each scan and once a day the plugin sends a short summary to `https://secaudit.eu/api/agency/report`: the score, the number of findings per level, the time of the last scan, the number of waiting WordPress, plugin and theme updates, the WordPress, PHP and plugin versions, whether the site uses HTTPS and the share of administrators with two-step login, and the findings of the latest scan so the panel can list them (for each finding its level, its kind, a short title and the plugin, theme or component it concerns; at most 200; findings you marked as false alarms are left out). No file paths, user names, page or post content, passwords or keys, or visitor data are sent. Unlinking (here or in the panel) calls `https://secaudit.eu/api/agency/unlink` and secaudit.eu deletes the site's data. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu affiliate programme (opt-in, only for sites linked to the agency panel) =
Only for a site linked to an agency panel account (Settings → Agency panel), and only when an administrator opens the **Affiliate programme** card (Settings) or presses **Refresh** there; the answer is kept for about an hour and nothing is polled in the background. Sent to `https://secaudit.eu/api/agency/affiliate`: the site key of the agency link (in the Authorization header) and an empty body; nothing else. Received: whether the programme is open and allowed for this site and, if so, your referral code and link, the share you earn, the minimum payout and your figures (visits of the last 30 days, number of sales, amounts earned and available). The referral code and link are stored on your site only while the site is in the programme, and deleted when it is not, when you unlink the site and when you delete the plugin. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu affiliate banners (opt-in) =
Only if an administrator uses the banner feature of the Affiliate programme card. The banner image chosen there is downloaded once from `https://secaudit.eu/assets/affiliate/` (file names like `secaudit-en-300x250.gif`, built only from the fixed list of sizes, languages and colours; only a GIF of the expected size, under 600 KB, is accepted) and stored in `wp-content/uploads/secaudit-banners/`. If a banner you placed is missing there, a single scheduled task downloads it; it is never downloaded while a visitor loads a page. Sent: nothing except a normal HTTPS request with a generic user agent, without your site address. Banners are shown only where you place the `[secaudit_banner]` shortcode, the "secaudit banner" block or the "secaudit banner" widget, and only while the site is in the programme. Visitors' browsers load the image from your site, never from secaudit.eu, and no visitor data is sent anywhere. Banner links go to your referral link on secaudit.eu and are marked `rel="sponsored"`; an "Advertisement" label above the banner is on by default. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu off-site scanning (opt-in) =
Only if you turn on off-site scanning (Scans → Off-site scanning), and then only during malware scans. Turning it on sends your domain, WordPress path and plugin version to `https://secaudit.eu/api/offsite/join` with the same domain check by a one-time token. During a scan the plugin first sends the SHA-256 fingerprints of the code files it cannot check on its own (in plugins, themes and uploads; not WordPress files or plugin files that match the checksums WordPress.org publishes) to `https://secaudit.eu/api/offsite/check`; only files whose content secaudit.eu has not scanned before are then sent, compressed, to `https://secaudit.eu/api/offsite/scan`, without file names or paths. wp-config.php, must-use plugins and other files directly in wp-content (such as object-cache.php, which often hold passwords), database dumps, logs, backups, key files and images without code are never sent; they and files larger than 1 MB are scanned on your site. secaudit.eu scans the content in memory and does not keep it; it keeps only fingerprint → result (not linked to your site). Files may contain personal data in rare cases. Turning it off calls `https://secaudit.eu/api/offsite/leave`, which deletes the site on secaudit.eu. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu Pro license (only if you enter a key) =
Only if you enter a secaudit.eu Pro license key (Settings → secaudit.eu Pro). Activating sends the key, your domain, WordPress path and plugin version to `https://secaudit.eu/api/license/activate`; secaudit.eu confirms that the site belongs to the domain by calling back a REST endpoint on your site with a one-time token (as for the verification page) and counts the site as one seat of the license (each subdomain is its own seat). Once a day the plugin sends the key, domain and path to `https://secaudit.eu/api/license/status` to learn whether the license is still active. **Remove the license** (and deleting the plugin) calls `https://secaudit.eu/api/license/release`, which frees the seat. The key is stored in your database. No site content, user data or visitor data is sent. Provided by AngelForce OÜ; payments are handled at the checkout on secaudit.eu, never by the plugin. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

= secaudit.eu urgent vulnerability alerts (Pro, opt-in) =
Only with an active Pro license and only if you turn on **Urgent vulnerability alerts** on the license card. Then, at once and with the daily license check, the plugin sends the names (slugs) and versions of your installed plugins and themes, the WordPress version, the PHP version and, on Debian or Ubuntu servers, the version of the PHP package (read from the package's changelog) to `https://secaudit.eu/api/license/inventory`. secaudit.eu compares this list with its vulnerability database and with urgent advisories, and e-mails the license owner when a serious vulnerability (actively exploited, or rated 7 or higher and published in the last 14 days) affects one of these versions. Each vulnerability is mailed once. Turning the option off calls `https://secaudit.eu/api/license/inventory-off`, which deletes the list on secaudit.eu; removing the license, or a license that is no longer active, deletes it too. Without this option the names of your plugins and themes are never sent. Provided by AngelForce OÜ. [Terms](https://secaudit.eu/terms/), [Privacy policy](https://secaudit.eu/privacy/)

== Changelog ==

= 0.31.2 =
* Hacked Site Cleanup: the cleanup-service contact was removed. The page keeps the summary to give to a developer or hosting company.
* Trust badge: the label says "Security Checked" ("Security & Compliance Check" on the plaque, "Check outdated" after 30 days) instead of the earlier audit wording, in every language. The checks are automated; nobody audits the site by hand.
* Signed reports: the card and this readme describe early access: every site can use them at no charge until secaudit.eu ends early access, which it announces at least 14 days ahead.
* Affiliate programme: while it is not open, the card says so ("The affiliate programme is not open yet.").
* Wording: "use it only on sites you own or manage" in the plugin footer, the plugin description and this readme.

= 0.31.1 =
* "Show changes" no longer puts an empty line after every added or removed line; copied text and the view without styles keep one line per line.
* Turning the firewall on no longer makes the next file-change scan report the plugin's own loader (`mu-plugins/secaudit-firewall.php`) as "added outside WordPress". Only the exact file the plugin writes is recognised; a changed loader is still reported.
* Hacked Site Cleanup counts high-severity added or changed files from the file-change scan (for example a web shell in uploads) as signs of an attack, and "Signs of compromise" has a row for them, so the page no longer says "No signs of an attack" while step 4 lists such a file.
* The "next step" text on the Dashboard and on Compliance & Reports is a plain summary ("1 file was added outside WordPress — check it") instead of the cut start of the finding with its date.
* Hacked Site Cleanup, step 4: the file row shows the whole description including the added lines; it ended in an empty "Added:" before. A file change whose time is unknown is described without a date.
* Compliance & Reports: next to an expanded admin menu the count of a row (for example A07) no longer overlaps its name; in a narrow card the count moves under the name, and "1 high · 1 medium" breaks only between the parts.

= 0.31.0 =
* In short (0.29 to 0.31): file changes explain themselves and can be accepted one by one (0.29); signed report documents are made by secaudit.eu, the compliance summary is on screen, and the plugin checks that its own files are the original release (0.30); a new look shared with secaudit.eu, light or dark (0.31).
* Security Dashboard and Compliance & Reports follow the approved secaudit design. Every page starts with one "next step" card: what the state is, what to do next, one main button. The dashboard shows the score, open findings, blocked attacks, files checked, the trust seal, findings by severity, hardening and the audit trail; server health and active protection moved to Protection, signs of compromise to Hacked Site Cleanup, the free secaudit.eu services to Settings → Services. Compliance & Reports shows the report record (site, report code, date, scope) in a navy band, then NIS2 Article 21(2), OWASP Top 10:2025 and GDPR Article 32 with an icon, a word and the number of findings per measure ("N to review" counts measures with at least one high or medium finding), the report history and the note that this is a technical view of your site, not a legal assessment. Scans lists the open findings as a numbered to-do list with the actions on each row; the Event Log shows each event as time, icon and sentence. Severity icons: ✕ in a circle critical, ▲ high, ◆ medium, ● low, ✓ in a circle no findings.
* Signed reports (PDF, CSV) follow secaudit.eu: during early access every site gets them and the Signed report card shows the download buttons. Once secaudit.eu answers that a report needs Pro, a site without an active Pro license sees "Pro" and "See plans" instead (for 7 days, then it asks again). The `wpsa_signed_reports_open` filter can change this.
* New look, the same as secaudit.eu and its panel: light pages by default with white cards, navy headings and the secaudit green for buttons and links, the Manrope and IBM Plex Mono fonts (bundled with the plugin, nothing is loaded from other sites). Dark pages follow your device, or choose Light, Dark or "Same as this device" under Settings → General → Appearance; the choice is kept per user.
* The plugin's own menu column is gone: its pages are in the WordPress menu (secaudit → Dashboard, Scans, …). Every page starts with the same header: the secaudit.eu shield, the page title, the site and the time of the last scan, and at most one main and one second button.
* Status is always shown with an icon and a word (OK, warning, critical, high, medium, low), never by colour alone; messages look like WordPress notices with the status colour on the left. Colours meet WCAG AA contrast in light and dark.
* Settings are split into General, Services, Alerts, Pro & AI and Agency & Affiliate, each with its own save button. The decorative secaudit.eu label on the setting cards was removed.
* Phones: tables (Event Log, live traffic, plugins and themes, users, quarantine, firewall) become one card per row with "label: value" pairs instead of breaking words letter by letter; the four summary tiles on the dashboard sit two by two; severity labels stay next to the finding.
* Trust badge: three styles instead of five: plaque (default), seal and bar. Saved styles move to the nearest one (the navy seal and the medallion become the seal, the card becomes the plaque); `[secaudit_badge style="…"]` keeps working with the old names. The seal's ring text is fitted to the ring, so long labels and translations are no longer cut off ("SECURITY AUDITE…"). Brand navy instead of the blue gradient, Manrope where the site has it and the system font otherwise. The honesty rules are unchanged: no badge below 60, "Audit outdated" after 30 days.

= 0.30.0 =
* Change: report documents are now a secaudit.eu service. Compliance & Reports shows a **compliance summary** on screen: for NIS2 Article 21, GDPR Article 32 and OWASP Top 10, every measure or category with its status and the number of open findings. **Download signed report (PDF)** and **(CSV)** send the report content (in the site language, user names shortened, no file contents) to secaudit.eu, which lays out the document and returns it with a report code. Signed reports are part of secaudit.eu Pro; during early access they work for everyone. The plugin no longer builds PDF, CSV or printable report pages itself (the PDF writer and its fonts were removed), and `wp secaudit report` asks the service too.
* New: all open findings are listed on the Scans page (they were in the old report view), with the severity filter and the buttons under each finding.
* New: the plugin checks that its own files are the original release. Once a day and after an update it downloads the signed file list of its version from secaudit.eu and compares every file; a changed, missing or added file is shown on the plugin's pages ("This copy of the plugin has been changed…") and is reported to the verification page, which then does not show the seal as verified. Turn it off on development copies with the `wpsa_self_check` filter.
* New: with an active Pro license, new firewall rules and malware signatures come straight from secaudit.eu without the 30-day wait of the public files; if that fails, the public files are used as before. The signature check is unchanged.
* Vulnerabilities: for a plugin or theme that is not from WordPress.org, when the fixed version is not offered in your dashboard the finding no longer says "update now"; it says that the fix is listed as that version, which your dashboard does not offer, to check the vendor's download page or support, and to consider deactivating it until then (virtual patching helps where available).

= 0.29.0 =
* New: file changes explain themselves. The baseline now keeps a copy of your own small text files (files in the site root that are not WordPress's, wp-content drop-ins such as advanced-cache.php and object-cache.php, mu-plugins, PHP in uploads, and plugins and themes that are not from WordPress.org; at most 2000 files, 64 MB, under uploads/wpsa-baseline, locked from the web). A changed file is then reported with who changed it (the dashboard file editor and which user; a caching plugin that writes the drop-in; a "# BEGIN … # END" block of a plugin in .htaccess; WordPress rewriting its rewrite rules; a known setting in wp-config.php; or something outside WordPress such as FTP, SSH, a deployment or an attacker), when, how many lines were added and removed and up to three added lines, with a "Show changes" diff. Secrets in wp-config.php (database settings, keys, salts, anything named KEY, SECRET, TOKEN or PASS) are masked in the copy and in the diff, so they are never shown.
* New: the severity of a changed file follows what was added: high when the added lines can run commands or hide code (eval, base64_decode, system, input passed to include, hex and chr chains, remote includes, scripts and redirects to unknown hosts, new administrator accounts, .htaccess redirects elsewhere or handlers that run images as PHP, auto_prepend_file); info when a known writer or the dashboard editor made the change; otherwise medium ("review the change") instead of high for every changed file.
* New: "This change is mine" under each changed or added file and "Forget this file" under a deleted one: accepts that one file into the baseline (and refreshes its copy) without a new baseline, removes the finding and writes it to the event log. The .htaccess and wp-config.php findings can be accepted too.
* New: edits in the dashboard file editors (Appearance → Theme File Editor, Plugins → Plugin File Editor) are written to the event log with the user and the file.
* Vulnerabilities: for a plugin or theme that is not from WordPress.org, the finding now says whether the vendor's update to the fixed version is already offered in your dashboard, or that you have to get the fixed version from the vendor because WordPress cannot update it on its own.
* An existing baseline is upgraded in the background (in small batches through a single scheduled task; on sites without cron, while you use the plugin's pages): every unchanged file gets its copy. A file that had already changed before the update is still explained from its current content, with a note that the earlier content is not available; accepting it stores the copy.
* Deleting the plugin also removes the baseline copies (uploads/wpsa-baseline).
* Fix: long base64 blocks that decode to an image or a font (theme preview templates, icon sets) no longer raise "long base64 block" or "high entropy" findings; encoded code is still reported.
* Change: code-check findings in a plugin file that is identical to the official WordPress.org release are reported as low (the plugin author's code, covered by the vulnerability database), not high.
* Change: a public (nopriv) AJAX action that verifies a signed token or stored secret key (hash_equals / hash_hmac) instead of a nonce is reported as low, with a note, not high.

= 0.28.1 =
* Fix: a WordPress file such as wp-config-sample.php was reported as "modified" when WordPress was installed from a translated package but the site language is another one (for example installed in Turkish, site language English), or when WordPress had updated itself with the English package. A file that matches the official file of the same WordPress version in the installation package's language, in an installed WordPress language or in English is now accepted as official.

= 0.28.0 =
* New: Affiliate programme card (Settings), for sites linked to the agency panel. It shows whether the secaudit.eu affiliate programme is open for this site, how to join, and once joined your referral link (with a Copy button), visits, sales and earnings, and a link to the payout page. The status is asked only when you open the card or press Refresh.
* New: affiliate banners as the `[secaudit_banner]` shortcode, the "secaudit banner" block and the "secaudit banner" widget (size, language, colour, "Advertisement" label). Banner images are downloaded once and served from your own site, so visitors never load anything from secaudit.eu; links are marked `rel="sponsored"`. Nothing is shown unless you place a banner, and only while the site is in the programme.
* Deleting the plugin also removes the downloaded banners (uploads/secaudit-banners) and the banner widget settings.

= 0.27.0 =
* New: secaudit.eu Pro license card (Settings → secaudit.eu Pro). Enter the key from the mail to use AI assistance, off-site scanning, downtime alerts and the agency panel once secaudit.eu requires a license for them; the card shows the plan, the seats in use and the renewal date. Removing the license (or deleting the plugin) frees the seat for another site.
* New: urgent vulnerability alerts by e-mail (Pro, opt-in): secaudit.eu checks the versions of your plugins, themes, WordPress and PHP against new, serious vulnerabilities as soon as they are known, not only at your next scan.
* When a secaudit.eu service needs a license, the plugin now says so clearly instead of showing a general error.

= 0.26.7 =
* The security score falls less steeply: the first findings lower it most and every further one a little less, so a site with several serious findings no longer sits at 0 and each fix shows in the score. One high finding gives about 89, four about 62; signs of an attack still keep the score below the seal.
* The check for encoded or encrypted code skips plugin files that match the official WordPress.org copy and translation files that only hold text (*.l10n.php).

= 0.26.6 =
* Notices of other plugins (review requests, licence and advertising banners) are no longer shown on this plugin's own pages, where they pushed the results out of sight. This plugin's own warnings and WordPress's update and maintenance notices stay; other admin pages are unchanged. Developers can turn this off with the `wpsa_hide_other_notices` filter.

= 0.26.5 =
* Agency panel: a linked site now also sends the findings of its latest scan (level, kind, a short title and the plugin, theme or component), so the panel at app.secaudit.eu can list what needs doing. File paths, user names, page or post content and secrets are never sent; findings you marked as false alarms are left out.

= 0.26.4 =
* Backups, logs and other files that should not be public are now tried over the web: if anyone can download the file the finding says so (with its address) and is at least medium; if the web server blocks it, the finding is one level lower.
* Known vulnerabilities in an inactive plugin or theme are one level lower (its code does not run on your pages), unless attackers are using the vulnerability or it is rated 9 or more. Being out of date without a known vulnerability is no longer high: medium for an active plugin a major version behind, otherwise low.
* Report: hard-coded passwords and keys are mapped to OWASP A07 (authentication failures), scripts in posts and comments to A05 (injection); cron tasks without an action have no OWASP category.

= 0.26.3 =
* Far fewer false alarms in the scans. Plugin files that match the official WordPress.org copy are no longer searched for risky code (library code such as phpseclib or webp-express raised dozens of warnings); changed or extra files are still scanned.
* <script> in posts and settings: structured data (ld+json) and templates are ignored; known tracking, chat and consent services (Google, Meta, Hotjar and others) are listed as information; a script from an unknown address, or with hidden code, stays high. Settings with the same content (such as a theme's backup) are reported once.
* Links: links to your own site (with or without www) and to .gov, .edu and .int sites no longer count as external; a "link cluster" now means links to 10 or more different websites.
* Private keys and AWS keys are reported only when a real key is there (not the header text in a library); password and AWS checks skip library and built files.
* The same risky function in one file is one finding with a count. Cron tasks without an action (left by removed plugins) are low. Backups of code and settings files stay medium, other backups are low. Contact Form 7's upload checks are recognised; CSRF findings in handlers that check permissions, and open meta fields, are low.
* GDPR Art. 33 (breach notification) is shown only next to strong signs of a breach.
* New checks: PHP error_log files in the site folders, PHP version shown in the X-Powered-By header, and PHP files in wp-includes that answer with a server error when opened directly.

= 0.26.2 =
* The public verification page is kept honest after a plugin is removed: while the listing is on, the plugin checks in with secaudit.eu once a day; after 3 days without a check-in the seal pauses and the page no longer links to the site (it resumes with the next check-in). Visitors who follow a seal that is paused or expired see a warning.

= 0.26.1 =
* The trust badge settings now show where the [secaudit_badge] shortcode is used, and how to add it (page, footer, widgets) when it is not on any page yet.

= 0.26.0 =
Seven features other security plugins have, all off until you switch them on.
* New: request limit — an address that asks for more than 60, 120 or 240 pages a minute gets "429 too many requests" for 10 minutes (Protection → Bots and page probing). IPv6 counted per /64. Logged-in users, the "Never block" list, administrators' addresses and verified search engine crawlers are never paused; REST requests with an application password are counted only when the password is wrong.
* New: two-step login with a code by email, for people without an authenticator app (10 minutes, one sign-in, at most 3 codes, backup codes still work).
* New: "Trust this browser for 30 days" on the two-step code page (opt-in, Identity & Access). Forgotten when the password changes, when two-step login is turned off, after "Sign everyone out" or when the option is turned off.
* New: accounts overview with last sign-in; suspend and restore accounts (every way of signing in is closed and open sessions end); optional automatic suspension of content accounts after 90, 180 or 365 days without signing in (never administrators); temporary administrator or editor access for an existing user, and temporary accounts that are suspended when their time is up.
* New: chat alerts to Slack, Discord, Google Chat or a JSON webhook for important events, with a test button; the address is shown only in part.
* New: low disk alert by email (and chat), at most once a day.
* Improved: the database spam check now covers every public content type (products, events…), reusable blocks, templates, private and scheduled posts, and approved comments.
* Turning two-step login off from a profile is now written to the event log (as high).
* Improved: the PHP vulnerability check (PHP2Shell) now reads the PHP package changelog of Debian and Ubuntu when the server allows it: a fixed package that is running clears the finding by itself, a fixed package that is installed but not yet running asks for a PHP restart, and a missing fix asks for the distribution's security update instead of a version number the distribution never ships.
* Turning on email codes now needs a code from a test email, so nobody is locked out by a site that cannot send email. Two-step codes are counted before they are checked, so parallel guesses cannot exceed 5.

= 0.25.0 =
* New: live traffic on the Event Log page (opt-in): every request as it happens, with filters (visitors, logged in, search engines and bots, blocked, page not found) and a Block button. Kept 24 hours; search words, reset keys, email addresses and other typed values are not recorded; turning it off or deactivating the plugin deletes the list; included in WordPress's personal data export and erase.
* New: sign-in link by email (opt-in): "Email me a sign-in link instead" on the login page. One use, 15 minutes, confirmation page with the account name against email scanners and misuse, same answer whether or not an account exists, 5 requests per address, 3 per account and 30 per site an hour, robot check when on, WordPress's password-reset rules, two-factor code still asked. Also a way back in after a login lockout.
* The suggested privacy policy text now mentions live traffic and sign-in alerts when they are on.

= 0.24.0 =
Account and bot protections that other security plugins have, all off until you switch them on.
* New: page probing lock — an address asking for many missing pages in a few minutes is blocked for 30 minutes (static files, favicon and robots.txt not counted; logged-in users, your "Never block" list and verified search engine crawlers never locked).
* New: fake search engine crawler block — requests that claim to be Google, Bing, Yandex, Baidu, Apple or Yahoo are verified by reverse and forward DNS; nothing is blocked when the check cannot be done.
* New: both stay off automatically when the site is behind a proxy server whose visitor addresses WordPress cannot read, with a note on the card.
* New: sign-in alerts — an email when an account signs in from a new browser or device, after two-factor authentication.
* New: ask for new passwords — sign out everyone with the chosen roles, delete their application passwords (and optionally their passkeys) and have them set a new password through an emailed link; optional password age limit (90, 180 or 365 days). `wp secaudit password-renew` shows or cancels it.
* New: application passwords — WordPress default, administrators only, or off.
* New: sign out after 15 minutes to 4 hours without activity (background "heartbeat" requests do not count as activity; autosaves while typing do).
* New: download the event log as CSV.
* New: the server health card shows outages seen from outside in the last 30 days (with downtime alerts on).
* A refused sign-in because a new password is needed is not counted as a failed login (no lockout, no "failed login" entry).
* Firewall and bot "403" pages are now marked as not cacheable for page caches as well.
* 124 new texts translated into German, Spanish, French, Italian, Polish, Portuguese and Turkish.

= 0.23.1 =
* The plugin's name is now "secaudit – Security Audit & Compliance". Nothing else changes: same folder, settings and features.

= 0.23.0 =
Security and reliability release after an independent review of the whole plugin.

* Firewall: a crafted request could keep the server busy for seconds (slow pattern on long input); the patterns are now linear. Long values are checked in full (in overlapping parts) instead of only their first 64 KB, the same field in the URL and the form is checked separately, and a request too large to check (over 2 MB or 5,000 values) from a visitor who is not logged in is blocked in block mode instead of passing unchecked; for logged-in users and REST clients with credentials it is only logged. Rules from secaudit.eu are fetched without redirects, refused if older than the ones already installed, and each rule is tested against slow input before use.
* Malware scan: folders where attackers usually hide files (uploads, must-use plugins, the WordPress and wp-content folders, PHP files in caches) are checked first, and a scan cut short by the time or file limit now says so instead of "no matches". Files over 2 MB are checked in parts; PHP files over 16 MB are reported.
* WordPress core files: the official checksums are fetched over HTTPS only, and a site that cannot be checked gets a finding instead of a silent pass. The plugin also checks its own files against the list of files in its package.
* Repair: a premium or custom plugin with the same folder name as a different WordPress.org plugin is no longer "repaired" with the WordPress.org code (its Update URI header is respected). Files bundled with WordPress in wp-content (Akismet, default themes) are no longer replaced with older copies. A failed repair no longer leaves a broken file. With the file-change lock or DISALLOW_FILE_MODS on, files cannot be put back from quarantine or replaced from the dashboard, and only files shown by the latest scan can be moved or replaced.
* Shared attacker list: the list is stored in a protected folder with a random name instead of a public uploads folder.
* Off-site scanning: must-use plugins and files directly in wp-content (such as object-cache.php, which often hold passwords) are now always scanned on your site and never sent.
* Two-step login: an account that must set it up but has not has no permissions until it does, also through the REST API and AJAX.
* Unlocking the file-change lock allows 5 wrong passwords per 15 minutes.
* Event log: failed logins with very long user names or from one address in a flood no longer fill the database; the log keeps at most 100,000 entries.
* Scans no longer stop with an error when one folder cannot be read (common on shared hosting); the weak password check remembers passwords it already checked and stops in time on sites with many administrators; the code check stops on files built to slow it down.
* Fixed: the trust badge could show before the first scan; off-site malware results were missing from the public check, the dashboard and the reports; ClamAV errors were shown as a clean result, and the public verification no longer counts an incomplete malware scan as passed; the agency panel always showed 0 updates from scheduled reports; a spam word inside another word (such as "timeslot") with any link became a critical finding; on a network, the IP block list did not apply to sub-sites with the early firewall, and network-activated plugins were treated as inactive; virtual patches stayed after a plugin was deactivated; some scheduled tasks stayed after deactivation; site names with "&" were shown as "&amp;" in alert e-mails; a fatal error without the PHP mbstring extension; several untranslated log messages.
* Server health no longer raises the memory limit or writes to the database after a fatal error, and no longer samples 404 pages.

= 0.22.2 =
* Fixed: an affected PHP version (PHP2Shell) was shown as a sign that the site had been hacked, with a link to the cleanup steps. It is a known vulnerability to update, not an attack: it now counts as a high finding and only keeps the trust badge hidden.

= 0.22.1 =
* PHP2Shell check: the site's PHP version is compared with the fixed releases for CVE-2026-109618 and CVE-2026-109619 (critical vulnerabilities in PHP itself, published on 21 October 2026). An affected PHP is a high-severity finding with the version to update to; a PHP version that no longer gets fixes is told to move to a supported one. Linux distributions often fix PHP without changing its version number: a PHP built after the fix with an old number is reported as "check with your host" instead, and can be marked as a false alarm. The check runs on this site only, nothing is sent anywhere, and it shows right after the update.
* PHP end-of-life dates updated to the PHP project's current schedule (8.2 until 31 December 2026, 8.3 until 2027, 8.4 until 2028).

= 0.22.0 =
* Downtime and certificate alerts (optional, secaudit.eu): checks from outside every 5 minutes, certificate once a day, e-mail when the site is down, back, or the certificate is about to expire.
* Agency panel (optional, secaudit.eu): several sites in one account, with score, findings, updates and versions.
* Off-site scanning (optional, secaudit.eu): the heavy malware scan runs on secaudit.eu; code files are sent without names or paths and not kept.
* AI assistance (optional, secaudit.eu): Explain and Second opinion for findings, incident notification drafts, and advice on the Server health card.
* Cleanup page: a contact button for cleanup help (removed in 0.31.2).
* Fixed: on a site that sets visitors' addresses with the `wpsa_client_ip` filter (behind a proxy other than Cloudflare), the firewall's early start used the proxy's address, so a ban could block every visitor. The early start now waits for such a filter, and the address is worked out again once the plugins have loaded.

= 0.21.6 =
* Firewall early start: the small loader in wp-content/mu-plugins now finds the plugin through the plugins folder instead of a fixed server path, and loads nothing while the plugin is inactive. A site that was moved, or a leftover copy of the plugin folder, can no longer make it load the wrong copy. Loaders written by 0.21.3–0.21.5 are replaced on the next admin page.

= 0.21.5 =
* Custom login address on multisite networks: one address for every site (after each site's own address); links WordPress builds to another site's login page use that site's address. An address that is a site's folder name is refused.
* Separate GDPR Article 32 and NIS2 Article 21 reports on the Compliance page, next to the combined report. The NIS2 report lists all ten measures and says plainly which ones a website scan cannot cover.

= 0.21.4 =
* Server health on the dashboard: page build time, PHP memory, pages stopped by "out of memory" or the time limit, server load, PHP version, OPcache, object cache, free disk, late scheduled tasks and autoloaded settings. It says whether the server keeps up, which settings to change, or what a server for this site should have. Measured on your site only; nothing is sent.
* Firewall rules feed: error messages appear in the language of whoever opens the page.

= 0.21.3 =
* Robot check on the comment form (optional; works with page caching) and at most 5 comments per address in 10 minutes.
* Safe mode: `define( 'WPSA_SAFE_MODE', true );` in wp-config.php pauses everything that can lock you out; WP-CLI `wp secaudit disable <protection|all>` and `wp secaudit twofa-off <user>`.
* Address lists: never block / always block, and the addresses locked right now with an Unlock button.
* Two-step login can be required for chosen roles.
* Firewall: extra signed rules from secaudit.eu, and it starts before other plugins load. Fixed: the built-in pattern for probes of secret files (.env, .git, config backups) did not work since 0.15.0.
* New checks: default security keys, `wp_` table prefix, world-writable folders and files, phpinfo pages, `.git` / `.svn` in the web root, the WordPress version in the page source (with a switch to hide it), mixed content; themes not from WordPress.org; four more code checks (low capability, setting names or sensitive settings from user input, meta fields with auth_callback __return_true, links that change data without a nonce).
* Event log: new and deleted users, plugin and theme installs, updates and deletions, WordPress updates and file changes; every message in the site's language.
* Dashboard: attacks blocked in the last 24 hours, the score's change in 30 days and the score once the top findings are fixed.
* Reports: file and line for code findings; user names shortened.

= 0.21.2 =
* The panel has its own menu on the left, as in the original design: every page with its icon, the open page
  highlighted, Settings at the bottom. It stays in view while you scroll; on narrow screens it becomes one row
  at the top. The WordPress admin menu keeps the same pages.

= 0.21.1 =
* Code vulnerabilities: missing authorization in AJAX and admin-post actions. Any signed-in user, even a
  subscriber, can call these actions; the check reports actions that change settings, users, roles, plugins,
  themes or files, sign someone in, or delete posts, without a permission check (current_user_can) or a nonce,
  including actions that only check is_admin(), which is true for every admin-ajax.php request. Actions open to
  visitors who are not signed in are reported per action, with the line. The severity is higher when the request
  decides what is changed. The action's code is followed into the same plugin's other files and into its helpers;
  a permission condition around the hook, a guard on the same action, a helper named like a permission check, or
  an ownership check means no report.
* Reports: "Download PDF" on Compliance & Reports creates the report as a PDF file (findings sorted by severity,
  OWASP / GDPR / NIS2 columns, false alarms listed separately, page numbers), with no outside service. The font is
  embedded, so the PDF looks the same everywhere and its text can be searched and copied.
* WP-CLI: `wp secaudit report <file>` writes the report as PDF, CSV or HTML.

= 0.21.0 =
* Sessions (Identity & Access → Sessions): the longest time a sign-in lasts (sign-ins already open are shortened
  too), and how many sign-ins an account may have at the same time (the oldest ends once a new sign-in is
  complete, after two-factor authentication, so a password alone cannot sign anyone out). Both apply to
  accounts that can write or manage content; customers and subscribers are not affected.
* Configuration check: "Remember me" sign-ins kept valid for more than 30 days by a plugin or theme; sign-in
  cookies without the Secure flag on an HTTPS site (WordPress does not see HTTPS behind some proxies and CDNs,
  or a filter turns it off); administrators signed in on four or more devices at once.
* Configuration check for WPGraphQL: debug mode, public schema introspection, tracing or query logs shown to
  every visitor, and no query depth limit.
* Code vulnerabilities: PHP sessions (session_start) in plugins and themes, with a warning when the session ID
  is never renewed (session fixation); sign-in or session cookies set without HttpOnly.
* Hardening: optional lock for installing, updating and deleting plugins and themes from the dashboard.
  Turning it off asks for the password again. Unlike DISALLOW_FILE_MODS, automatic security updates and
  language packs keep working, and the card lists the updates that are waiting.
* The compliance report maps the code checks added in 0.20.3 and this version to OWASP, GDPR and NIS2.

= 0.20.3 =
* Signs of compromise: translation files in wp-content/languages (*.l10n.php, which WordPress 6.5+ runs when it
  loads translations) are checked to hold only text; one with program code is a critical finding and can be
  quarantined. Other PHP files in the languages folder are reported too.
* Code vulnerabilities: file paths from user input in readfile/unlink/file_put_contents/copy/rename (path
  traversal); REST API routes without a permission check, or that change data while open to everyone; actions
  that logged-out visitors can call and that change settings, users or posts without any check; file uploads
  without a type check; CORS that lets any website make signed-in requests.

= 0.20.2 =
* Malware signature and hash findings now offer "Quarantine". They were stored with a path relative to
  wp-content, so the button could not find the file and was not shown.

= 0.20.1 =
* Security review fixes:
  * Adding a passkey now asks for the current password (5 tries per 15 minutes) and emails the user, so a
    stolen login session cannot quietly add a lasting way in.
  * The cleanup page can remove all passkeys (one an attacker added would survive "sign everyone out").
  * A passkey sign-in no longer overrides a login refusal by another plugin that ran before it.
  * The malware and code scans skipped folders next to the plugin's own whose names start the same way.
  * Multisite: a plugin task scheduled on a sub-site before network activation is removed instead of running
    there once and overwriting the network's results.

= 0.20.0 =
* Hacked Site Cleanup (new page): eleven steps in order — keep a copy, scan, put back original WordPress and
  plugin files (all at once, old copies to quarantine), quarantine unknown files, check administrators, sign
  everyone out, change passwords and keys, update, remove spam, scan again, notify. Steps the plugin can do
  have a button; the others can be marked as done. A plain-text summary for whoever helps you (nothing is sent).
  The dashboard points to the page when the last scan found signs of an attack.
* Fixed: the plugin's own empty index.php files in its data folders (country table, shared list, quarantine)
  were reported as "PHP in uploads" or "added file". New folders get an empty index.html instead.

= 0.19.0 =
* Multisite: when network-activated, the whole network is managed from the main site. Settings and
  protections (firewall, country blocking, robot check, passkeys, hidden user names, …) apply to every site;
  files are scanned once; the compromise and hidden-link scans check every site's database and name the site
  in each finding; one event log for the network, with the site address on events from other sites;
  scheduled tasks run on the main site only. Network Admin → secaudit lists the sites and their database findings.
* On multisite only network administrators can use the panel and its actions (repairs and quarantine change
  files of the whole network).
* WP-CLI refuses to run on a sub-site of a network-activated install (use the main site).

= 0.18.0 =
* Passkeys (Identity & Access → Passkeys, off by default): sign in with a fingerprint, face or device PIN
  instead of a password. Each person adds passkeys on their own profile; the login page gets a "Sign in with
  a passkey" button and passwords keep working. A passkey sign-in with user verification skips the
  two-factor code. Built in, no external service: supports ES256, RS256 and Ed25519 keys, checks the
  one-time challenge, origin, site address and signature counter (a copied passkey is refused and logged).
* WP-CLI: `wp secaudit passkeys <user> [--remove]`; `wp secaudit status` shows passkey sign-in.

= 0.17.0 =
* Shared attacker list (opt-in, Protection → Shared attacker list): blocked attacks are reported to
  secaudit.eu (IP address, kind of attack, time), and addresses that attacked at least five different sites
  in seven days are blocked on the login page or the whole site. Joining confirms the domain like the
  verification page; secaudit.eu stores a pseudonym of the domain and deletes reports after seven days.
  Leaving deletes the site and its reports. Suggested privacy-policy text under Settings → Privacy.
* New action wpsa_attack( $ip, $category ) fires when the firewall, a virtual patch or the login limits
  block an attack.

= 0.16.0 =
* Country blocking (Protection → Country blocking): block chosen countries or allow only chosen ones, on
  the login page, registration, password reset and XML-RPC, or on the whole site. The signed country table
  comes from secaudit.eu (IP Geolocation by DB-IP, CC BY 4.0) and is looked up on your own site.
* A rule that would block your own country is refused, so you cannot lock yourself out.
* Behind Cloudflare, the security features (login limits, robot check, firewall, country blocking, event
  log) now see the visitor's real address. Cloudflare's header is trusted only when the request really
  comes from a Cloudflare server; other proxies can use the new wpsa_client_ip filter.

= 0.15.0 =
* Firewall (Protection → Firewall, off by default): checks every request for SQL injection, cross-site
  scripting, file access, code and object injection, attack tools and probes for secret files. "Log only"
  records without blocking; "Block" returns a 403 page and blocks an address for an hour after 10 attacks
  in 10 minutes. Administrators and editors are not checked, passwords are never read, and free-text
  fields such as comments are only checked for PHP code.
* "Allow" switches one check off for one form field, straight from the list of recent events.
* The dashboard, the weekly summary and "wp secaudit status" show the firewall.
* New filter wpsa_firewall_match for custom rules.

= 0.14.0 =
* Robot check for the login, registration and password-reset forms: a small proof-of-work puzzle solved
  in the browser, made and checked on your own site (no Google or other outside service). Off by default;
  "On" or "Strict" under Protection → Login page.
* Custom login address: wp-login.php and wp-admin show "page not found" to visitors who are not logged in;
  all login, logout and password-reset links and emails use the new address. Recovery with
  WPSA_DISABLE_LOGIN_URL in wp-config.php or "wp secaudit login-url off".
* Hide user names (Identity & Access): closes ?author= addresses, the REST API user list for visitors,
  author names in embeds and the sitemap, and login and password-reset messages that reveal accounts.
* Breached password check (opt-in): rejects passwords found in data breaches, via the Pwned Passwords
  service of Have I Been Pwned with k-anonymity.
* Security headers in one click, each one separately, and an http:// to https:// redirect with a
  loop check.
* Weekly summary email (opt-in, under Scans → Automatic scans).
* WP-CLI commands: wp secaudit status, scan, findings, ignore, unignore, repair, quarantine, login-url,
  update-signatures, update-vulndb, send-summary.
* The configuration check now points to these settings, and the dashboard's hardening list shows them.

= 0.13.0 =
* Replace with the original: a changed WordPress core file or a changed file of a plugin from WordPress.org
  can be replaced with the official original from the finding. The download is checked against the
  official checksum before anything is written, and the previous copy is kept in quarantine.
* New check "Plugin files": compares every file of plugins from WordPress.org with the official version
  and finds changed files and PHP files that do not belong. It is part of the full scan.
* Quarantine: suspicious files are moved to a locked folder with a random name where they cannot run.
  The quarantine list on the File Integrity page puts files back or deletes them for good.
* Mark as false alarm: the finding no longer counts toward the score, the seal or the lists. Marks are
  written to the event log and listed separately in the compliance report and on the Scans page.
* Automatic security updates (opt-in, on the Vulnerabilities page): when an update fixes a known
  vulnerability in an installed plugin or theme, WordPress installs it automatically.
* Actions that change files ask for a second press first.
* The "Code and secrets" scan now covers every file of the site. Before, it stopped after the first 500
  files; on larger sites it now continues in steps with a progress bar.

= 0.12.1 =
* Drop-down lists (for example the automatic scan schedule) were hard to read on Windows: the open list
  showed light text on a white background. They now follow the dark panel and show an arrow.
* "About this site" for the public verification page: fields with links, other website addresses, e-mail
  addresses, phone numbers or markup are cleared when you save, and the form explains the rules.
  secaudit.eu applies the same rules and also refuses adult, gambling and other illegal content.

= 0.12.0 =
* The plugin now carries its maker's name: the admin menu is called "secaudit", every panel page shows
  the secaudit.eu logo, and the dashboard lists the free secaudit.eu services (vulnerability database,
  malware signatures, virtual patching, verification page, trust badge) and whether each is on.
* Cards whose data comes from secaudit.eu are marked with a small secaudit.eu label; reports, the CSV
  file name and alert e-mails mention secaudit.eu.
* The virtual-patch block page now points to "secaudit → Protection".

= 0.11.0 =
* The panel now works in place: moving between its pages and pressing any button no longer reloads the
  browser. The pressed button spins where it is (with a running clock on longer scans), a thin bar at the
  top shows loading, the result appears on the same screen, and messages pop up in the corner.
* Pages load ahead when you point at a menu link, so opening them feels instant. Back and Forward work.
* A long scan keeps running if you open another page meanwhile; you get a note with a "Show" button
  when it finishes. CSV export downloads without leaving the page.
* If the site does not answer or a security token has expired, you see a clear message and the page
  renews itself so the next try works. Without JavaScript every button works the classic way.
* Fix: a virtual-patch block that happens very early in a request no longer logs a WordPress
  "translation loaded too early" notice.

= 0.10.0 =
* Virtual patching: block attacks on a known vulnerability in one of your plugins or themes until you
  can update it, with no paid firewall. Rules are built from the free vulnerability database and only
  for a plugin that is installed, active and on an affected version; each rule checks only the requests
  that reach that plugin (its AJAX actions, REST routes, admin pages, shortcodes or the named parameter
  in the CVE), and is removed automatically when you update. Off by default; turn it on under Protection.
* Targets SQL injection, cross-site scripting, path traversal, object injection, code execution, unsafe
  file upload, SSRF, open redirect, cross-site request forgery and unauthenticated access, each matched
  only in its own vulnerability class. Administrators acting on their own site are never blocked.
* Try-it-out "log only" mode records what would be blocked without blocking; every patch can be turned
  off individually, and blocks are written to the security log.
* This is not a web application firewall and does not replace updating; it buys time until you update.

= 0.9.1 =
* Scans show that they are running: the button changes to "Scanning…", other scan buttons wait, and a
  note explains that it can take a minute. The result appears as a summary ("17 findings, 5 critical or high").
* The full file scan no longer depends on WP-Cron: while the panel is open, the page runs it in steps of
  a few seconds with a progress bar. Works on hosts with a broken cron or loopback, and in WordPress Playground.
* WordPress Playground: recognised automatically. Playground strips the comments from every WordPress
  file, which made every core file look modified ("may contain injected code"); that check is now skipped
  there with an explanation, and a note says which checks behave differently in Playground.
* The site risk summary no longer mentions WPScan or Patchstack; it reports the vulnerability database status.

= 0.9.0 =
* Known vulnerabilities without a paid API: the new secaudit.eu vulnerability database is built every
  night from the official CVE List (35,000+ records for 15,000+ plugins, themes and WordPress itself).
  Your site downloads only the signed parts that cover its own plugins and themes and compares versions
  locally; your plugin list is never sent. Off until you turn it on (one click on the dashboard).
* Each finding shows the CVE (linked to cve.org), CVSS, the version that fixes it, whether no fix has
  been released, and whether attackers are exploiting it. CVSS 9+ or actively exploited = critical.
* WordPress core: CVEs are only reported when WordPress.org itself lists your version as insecure, so
  fully patched older branches (security backports) are not flagged.
* Protection against tampering: wrong signature, wrong hash or an older (rolled-back) database is ignored.
* Removed the untested WPScan and Patchstack connectors; other sources can still be added with the
  `wpsa_cve_lookup` filter.

= 0.8.0 =
* New admin interface: a Security Audit menu with a dashboard and eight pages (Scans, File Integrity,
  Vulnerabilities, Protection, Identity & Access, Compliance & Reports, Event Log, Settings).
* Dashboard: security score with trend, open findings by severity, OWASP Top 10:2025 breakdown,
  hardening status, signs of compromise, active protection, plugin/theme/core issues, GDPR / NIS2 / CRA
  status, latest findings with a severity filter and the latest security events.
* "New Scan" runs every scan in one go; on large sites the file scan continues in the background.
* Fewer false alarms: plugin, theme and translation updates made through WordPress (including
  automatic updates) refresh the file baseline for exactly those files, and deleted plugins and themes
  are removed from it. Changes made any other way are still reported.
* Fonts, styles and scripts ship with the plugin; none are loaded from other sites.
* Old Tools → Security Audit links redirect to the new pages.
* NIS2 references now cite Art. 21(2)(a)–(j) precisely.
* All new text translated (German, French, Spanish, Italian, Polish, Portuguese, Turkish).

= 0.7.0 =
* Security fix: two-factor authentication was not enforced correctly and has been rewritten. Now:
  password, then a one-time 5-minute login token (HttpOnly cookie), then the code. A used code cannot be replayed, 5 wrong codes end the attempt and count
  toward the IP lock, backup codes are shown once after setup, and password logins over XML-RPC are
  refused for 2FA accounts (application passwords still work). Only the account owner can turn 2FA on;
  administrators can turn it off for a user who lost their phone.
* Malware signatures can update themselves every day from the signed secaudit.eu feed (opt-in). Every
  download is verified with an Ed25519 signature; a wrong signature changes nothing.
* Feed data is checked before use: every pattern must compile, hashes must be SHA-256, sizes are limited.
* Bundled signatures rewritten in English, with new detections for image/icon loaders, escaped include
  paths, hex-encoded eval, request-controlled function calls, socket backdoors and the WP-VCD family.
  Tested against 15,000+ clean WordPress core and popular plugin files with no false alarms.
* Fixed: the "reversed function names" signature never matched; images without PHP code no longer
  trigger signature matches. Signatures from the feed replace bundled ones with the same name.
* Far fewer false alarms: unmodified WordPress core files are recognised with the official checksums and
  skipped; a file in wp-admin or wp-includes that is not part of WordPress is reported instead.
  Method calls such as $pdo->exec() and SQL-free code are no longer flagged; risky-function findings
  are rated lower; CSRF is only reported where an AJAX or admin-post handler exists.
* New Configuration Check: HTTPS, debug output and public debug.log, risky default role for new users
  (a common hack), XML-RPC, user names exposed through the REST API, missing security headers and
  directory listing in uploads. Included in scheduled audits, the score and the compliance report.
* Code vulnerability scan follows user input through one variable assignment
  ($id = $_GET['id']; ... query("... $id")).
* The Code Scan now covers every file (it stopped after 500 before).
* Trust score: repeated findings of the same kind count up to three times, low findings are capped, and a
  critical compromise finding (malware, modified core, hidden admin, spam injection) hides the badge.
* "Not from WordPress.org" is now a neutral, low note for premium/custom plugins; if WordPress.org
  cannot be reached you get one notice instead of a finding per plugin.
* Security log retention (30, 90, 180 or 365 days; default 180) and optional IP shortening for GDPR,
  in Security Audit → Settings. The plugin adds suggested text to Settings → Privacy, and the
  WordPress tools for exporting and erasing personal data now include the security log.
* CSV export protects against spreadsheet formula injection and has a CVSS column for known vulnerabilities.
* Spam-link checks in widgets and theme settings now see external links correctly. Stored settings are
  read without creating PHP objects.
* Code checked against the WordPress.org Plugin Check rules. Internal hook names now use the plugin's own
  prefix: the `sac_cve_lookup` filter is now `wpsa_cve_lookup`; scheduled scans move to the new names
  automatically.
* If secaudit.eu's own look at your home page finds links hidden from visitors, the plugin now tells
  you so after publishing, instead of reporting success.
* After a scan the page jumps straight to the results.
* Badge shortcode lines up with the content in block themes, and the badge shows the date of the last
  scan again.
* All interface strings are now translated in every bundled language. Fixed translation files that
  older WordPress versions could not read.
* Deleting the plugin now removes all of its data (deactivating keeps it).
* New "External services" section in this readme.

= 0.6.1 =
* Reports use the OWASP Top 10:2025 categories (for example injection is now A05, vulnerable
  and unmaintained components are A03 Software Supply Chain Failures, misconfiguration is A02).

= 0.6.0 =
* Public verification page on secaudit.eu, added and updated automatically:
  the plugin proves domain control (one-time token + SHA-256 digest of the report),
  so nobody can publish or change the page for a site they do not run.
* Only a summary is sent (score, audit date, pass/fail for malware, core files and spam links).
  Detailed findings never leave the site.
* Optional "About this site" business card (company, description, country, field); links are not allowed.
* The page is refreshed after every scheduled scan, so the seal also drops when the score drops.
* Listing policy: secaudit.eu does not list adult, escort, gambling/betting, piracy or other illegal
  sites. It checks the domain name and the live home page on every update.
* Seal links now use rel="nofollow" (search-engine guidelines for widget links) and
  ?from=seal, which lets secaudit.eu warn visitors about copied seals.

= 0.5.0 =
* File-edit hardening: optionally disable the dashboard Theme/Plugin file editors
  (DISALLOW_FILE_EDIT + capability filter) to close a common PHP-injection path.
* Detects installed plugins that expose a file/code editor or run arbitrary code.
* Two-factor authentication (TOTP) for accounts.
* Internationalization phase 2: all finding messages translatable; Turkish complete.

= 0.4.0 =
* Internationalization: English source + 8 languages (de, fr, es, pt, it, pl, tr).
* Trust badge with Gold/Silver/Bronze tiers and five styles.
* Abuse & rate limiting (login throttle, XML-RPC, REST, spike alert).

= 0.3.0 =
* Malware signature scanning, hacklink/SEO-spam detection, code taint analysis.
* Compliance reporting (OWASP / GDPR / NIS2), event log, scheduled scans.

= 0.2.0 =
* File integrity, authentication audit, plugin/theme/core vulnerability checks.

= 0.1.0 =
* Initial local static scanner.

== Upgrade Notice ==

= 0.31.2 =
Wording only: the trust badge says "Checked", early access is described for signed reports, and the cleanup-service contact was removed.

= 0.31.1 =
Fixes: empty lines in "Show changes", the firewall loader reported as a changed file, the cleanup page missing a web shell, the next-step text, and an overlap on Compliance & Reports.

= 0.31.0 =
New look shared with secaudit.eu: light by default, dark with your device or a switch in Settings; pages in the WordPress menu; Settings in sections; the trust badge has three styles (old choices move to the nearest one).

= 0.30.0 =
Report documents (PDF, CSV) are now signed by secaudit.eu (Pro); the plugin shows a compliance summary on screen and checks that its own files are the original release.

= 0.29.0 =
Changed files now explain themselves (who, when, which lines, with a diff and secrets masked) and can be accepted one by one. An existing baseline gets its copies in the background after the update; nothing to do.

= 0.28.1 =
Fixes a false "modified WordPress file" alarm when WordPress was installed in another language than the site language.

= 0.28.0 =
Adds the optional secaudit.eu affiliate programme (for sites linked to the agency panel), with banners served from your own site. Nothing changes on your site unless you place a banner.

= 0.27.0 =
Adds the secaudit.eu Pro license card and optional urgent vulnerability alerts by e-mail. Nothing new is sent unless you enter a license key.

= 0.26.7 =
The score no longer drops to 0 after a few serious findings, and fewer false alarms from library files.

= 0.26.6 =
Other plugins' notices no longer cover the security dashboard.

= 0.26.5 =
A site linked to the agency panel also sends its latest findings (titles only, no file paths or personal data), so the panel can list them.

= 0.26.4 =
Exposed files are tested for download, and inactive or merely outdated plugins no longer raise high findings.

= 0.26.3 =
Scans give far fewer false alarms: official plugin files, structured data, known tracking scripts and links to your own site are no longer reported as risks.

= 0.26.2 =
With the public verification page on, the plugin now checks in once a day so the seal stays valid.

= 0.26.1 =
The trust badge settings show where the badge shortcode is placed, or how to place it.

= 0.26.0 =
New optional protections: request limit, email codes and trusted browsers for two-step login, account suspension and temporary access, chat alerts and a low disk alert.

= 0.25.0 =
Two new optional features: live traffic with one-click blocking, and sign-in links by email.

= 0.24.0 =
New, optional protections: page probing lock, fake crawler block, sign-in alerts, new passwords on request, application password control, sign-out when idle and event log download.

= 0.23.1 =
New plugin name only; no functional change.

= 0.23.0 =
Security release: fixes a firewall slowdown with crafted requests, scans that stopped early or reported "clean" when they were not complete, and repair of premium plugins with WordPress.org code. Update recommended.

= 0.22.2 =
Fixes 0.22.1 showing an affected PHP version as a sign of a hacked site.

= 0.22.1 =
Checks whether your PHP is affected by PHP2Shell (CVE-2026-109618, CVE-2026-109619), critical vulnerabilities in PHP itself.

= 0.22.0 =
New optional services from secaudit.eu: downtime alerts, agency panel, off-site scanning and AI assistance. All are off until you turn them on.

= 0.21.6 =
Safer firewall early start: the loader follows the plugins folder and does nothing while the plugin is inactive.

= 0.21.5 =
Custom login address on multisite networks, and separate GDPR and NIS2 reports.

= 0.21.4 =
Server health: find out whether your server is big enough for the site, and what it should have if not.

= 0.21.3 =
Comment robot check, safe mode, address lists, required two-step login, signed firewall rules, more checks, and a fix for the firewall's probe rule.

= 0.21.2 =
The panel has its own menu on the left again, with every page one click away.

= 0.21.1 =
New check for actions that change settings, users or plugins without a permission check; the report as a PDF download.

= 0.21.0 =
Session limits for staff accounts, session and cookie checks, a WPGraphQL settings check, and an optional lock on plugin and theme changes from the dashboard.

= 0.20.3 =
New checks: code hidden in translation files, and five more kinds of code vulnerabilities in plugins and themes.

= 0.20.2 =
Malware findings can be quarantined again from the results list.

= 0.20.1 =
Security fixes from a code review (passkeys, scans, multisite).

= 0.20.0 =
Step-by-step cleanup page for hacked sites; fixes false findings for the plugin's own data folders.

= 0.19.0 =
Multisite support. On multisite, the panel is now for network administrators only.

= 0.18.0 =
Optional passkey sign-in (fingerprint, face or PIN). Off until you turn it on.

= 0.17.0 =
Optional shared attacker list with other WordPress sites. Off until you join.

= 0.16.0 =
Country blocking with a locally searched country table, and correct visitor addresses behind Cloudflare.

= 0.15.0 =
A general firewall with a safe "log only" mode to try first. Off until you turn it on.

= 0.14.0 =
Robot check without Google, custom login address, hidden user names, breached password check, one-click security headers, weekly summary email and WP-CLI commands. All new protections are off until you turn them on.

= 0.13.0 =
Repair changed WordPress and plugin files from the official originals, quarantine suspicious files, mark false alarms, and optional automatic security updates.

= 0.12.1 =
Readable drop-down lists on Windows, and stricter plain-text rules for the public "About this site" text.

= 0.12.0 =
The admin menu is now called "secaudit"; the dashboard shows which free secaudit.eu services are on.

= 0.11.0 =
The panel now works in place: no page reloads, buttons spin where they are, results appear on the same screen.

= 0.10.0 =
New: virtual patching blocks attacks on known vulnerabilities in your plugins and themes until you update (free, off by default).

= 0.9.1 =
Scans now show their progress, the file scan works without WP-Cron, and WordPress Playground no longer shows false core-file alarms.

= 0.9.0 =
Free known-vulnerability checks (CVE) for your plugins, themes and WordPress, matched on your own site.

= 0.8.0 =
New Security Audit menu with a full dashboard. Old Tools links redirect automatically.

= 0.7.0 =
Security fix for two-factor authentication — please update. Also: signed daily signature updates (opt-in), far fewer false alarms, full translations.

= 0.6.0 =
Adds the automatic public verification page on secaudit.eu. Older versions can no longer publish.

= 0.5.0 =
Adds file-edit hardening, two-factor authentication, and fully translatable findings.

= 0.4.0 =
Adds multilingual support, an honest trust badge, and application-layer abuse protection.
