{"id":365481,"date":"2026-09-09T10:26:48","date_gmt":"2026-09-09T10:26:48","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/ini-protector\/"},"modified":"2026-09-10T00:44:21","modified_gmt":"2026-09-10T00:44:21","slug":"ini-protector","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/ini-protector\/","author":15146319,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.9.5","stable_tag":"1.9.5","tested":"7.1","requires":"5.7","requires_php":"7.4","requires_plugins":null,"header_name":"INI Protector","header_author":"ini software","header_description":"Lightweight WordPress hardening \u2014 file integrity monitoring with off-server alerts, TOTP two-factor authentication, login masking & lockout, security headers, information-disclosure protection, user-enumeration prevention, ALTCHA captcha, head cleanup, feed\/author privacy and more. Integrates with the INI WP control panel.","assets_banners_color":"39465b","last_updated":"2026-09-10 00:44:21","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/iniwp.com","rating":5,"author_block_rating":0,"active_installs":0,"downloads":119,"num_ratings":1,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","changelog"],"tags":{"1.9.2":{"tag":"1.9.2","author":"milenfrom","date":"2026-09-09 10:26:19","revision":3688107},"1.9.3":{"tag":"1.9.3","author":"milenfrom","date":"2026-09-09 18:57:09","revision":3688877},"1.9.4":{"tag":"1.9.4","author":"milenfrom","date":"2026-09-10 00:23:30","revision":3689145},"1.9.5":{"tag":"1.9.5","author":"milenfrom","date":"2026-09-10 00:44:21","revision":3689153}},"upgrade_notice":[],"ratings":{"1":0,"2":0,"3":0,"4":0,"5":1},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3688424,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3688424,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3688424,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3688424,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.9.2","1.9.3","1.9.4","1.9.5"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[168808,31093,602,600,9217],"plugin_category":[38,54],"plugin_contributors":[279916],"plugin_business_model":[],"class_list":["post-365481","plugin","type-plugin","status-publish","hentry","plugin_tags-file-integrity","plugin_tags-hardening","plugin_tags-login","plugin_tags-security","plugin_tags-two-factor","plugin_category-authentication","plugin_category-security-and-spam-protection","plugin_contributors-milenfrom","plugin_committers-milenfrom"],"banners":{"banner":"https:\/\/ps.w.org\/ini-protector\/assets\/banner-772x250.png?rev=3688424","banner_2x":"https:\/\/ps.w.org\/ini-protector\/assets\/banner-1544x500.png?rev=3688424","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/ini-protector\/assets\/icon-128x128.png?rev=3688424","icon_2x":"https:\/\/ps.w.org\/ini-protector\/assets\/icon-256x256.png?rev=3688424","generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>INI Protector is a focused, no-bloat hardening plugin for WordPress. Every feature\nis an independent toggle, grouped into three areas:<\/p>\n\n<p><strong>Security<\/strong><\/p>\n\n<ul>\n<li><strong>File integrity monitoring<\/strong> \u2014 hashes every code file (.php, .js, .htaccess \u2026),\nkeeps a baseline, and re-checks on a schedule. Any file that is added, changed,\nor deleted is emailed to you (and optionally POSTed to a webhook) <em>before<\/em> the\nbaseline is updated, so the evidence has already left the server even if the\nsite itself is compromised. Media is never hashed. Runs from WP-Cron or from\nsystem cron via <code>wp secwp integrity scan<\/code>.<\/li>\n<li><strong>Two-factor authentication (TOTP)<\/strong> \u2014 a time-based one-time code from any\nstandard authenticator app, required per role. The password is verified first,\nthen the code, before any session cookie is issued. Recovery codes are issued\nat setup, and <code>wp secwp 2fa reset &lt;user&gt;<\/code> restores access from the shell.<\/li>\n<li>Disable XML-RPC, disable the theme\/plugin file editor, require login for the\nREST API.<\/li>\n<li>Limit login attempts (IP lockout), mask the login URL to a secret slug.<\/li>\n<li>Password-protect the whole front-end.<\/li>\n<li>Disable comments and pingbacks\/trackbacks.<\/li>\n<li>Prevent user enumeration and information disclosure (directory listing,\nwp-config\/.htaccess\/backup\/log access; Apache .htaccess rules or an Nginx\nsnippet).<\/li>\n<li>Hardening HTTP security headers (X-Frame-Options, X-Content-Type-Options,\nReferrer-Policy, Permissions-Policy).<\/li>\n<li>ALTCHA proof-of-work login captcha (self-hosted, no third-party calls).<\/li>\n<li>Traffic monitor \u2014 records incoming requests so suspicious activity is visible,\nwith a per-IP drill-down. Keeps the full history by default; you can cap it by\nage, by number of requests, or both.<\/li>\n<li>Auto-block escalation \u2014 a dedicated <strong>IP Block<\/strong> page that surfaces the\ntraffic monitor's suggested blocks for one-click review (Block Suggestion\nSystem), and an optional <strong>Auto-Block<\/strong> mode that blocks offending IPs\nautomatically on an escalating temporary schedule (1h \u2192 4h \u2192 8h \u2192 5 days \u2192\n2 weeks). Never issues a permanent block automatically; an allowlist and\nverified search-engine bots are always exempt; quiet IPs decay back down.<\/li>\n<li>Vulnerability scan \u2014 a daily check of your installed plugins, themes, and\nWordPress core against the free WPVulnerability database (CC0, no API key).\nFindings appear on a dedicated page and on the dashboard, with an optional\nemail alert on new findings. No data about your site is sent \u2014 only the\npublic slug of each component is looked up.<\/li>\n<\/ul>\n\n<p><strong>Head cleanup<\/strong><\/p>\n\n<ul>\n<li>Remove the generator\/RSD\/WLW\/shortlink tags, strip or mask asset version\nquery strings, drop front-end Dashicons.<\/li>\n<\/ul>\n\n<p><strong>Utilities<\/strong><\/p>\n\n<ul>\n<li><strong>Rotate asset cache token<\/strong> \u2014 changes the version token on every CSS and JS\nURL at once, so returning visitors re-fetch them. Use it after a deploy when\na file has changed but the version it declares has not. Available from\n<strong>INI Protector \u2192 Utilities<\/strong> and from <code>wp secwp asset-salt rotate<\/code>, which is\nwhere it belongs in a deploy script.<\/li>\n<\/ul>\n\n<p><strong>SEO &amp; privacy<\/strong><\/p>\n\n<ul>\n<li>Disable feeds, disable author archives, obfuscate author slugs, and protect\nemail addresses from harvesting.<\/li>\n<\/ul>\n\n<p><strong>INI WP platform<\/strong><\/p>\n\n<ul>\n<li>Exposes a read-only, HMAC-signed REST endpoint (<code>secwp\/v1\/state<\/code>) so the\nINI WP control panel can pull this site's security posture and scan results.\nThe endpoint only activates when the INI WP connector is installed and\nconfigured; auth reuses the connector's signed channel.<\/li>\n<\/ul>\n\n<h3>Notes<\/h3>\n\n<ul>\n<li>Head cleanup lives here, not in SeoWP \u2014 SeoWP keeps pure SEO concerns\n(titles, meta, schema, noindex directives).<\/li>\n<\/ul>\n\n<h3>External services<\/h3>\n\n<p>The optional vulnerability scanner contacts WPVulnerability (https:\/\/www.wpvulnerability.com\/)\nonly when you enable Vulnerability Scan. It sends installed plugin\/theme slugs\nand the WordPress core version to https:\/\/www.wpvulnerability.net\/ to retrieve\nknown vulnerabilities. The service also receives the server IP address as part\nof the connection. No site URL is included in the plugin's user agent.\nService and privacy information: https:\/\/www.wpvulnerability.com\/privacy\/<\/p>\n\n<p>File integrity webhooks are optional. When you configure a webhook URL and enable\nalerts, reports containing the site URL, changed file paths, hashes and scan\nmetadata are sent to that URL. Configure only a recipient you trust; its terms\nand privacy policy apply. Email alerts use your site's configured mail service.<\/p>\n\n<p>The optional INI WP connector allows your configured control panel to retrieve\nsecurity settings and scan results through an authenticated REST endpoint.\nINI Protector does not initiate control-panel requests. Service information:\nhttps:\/\/iniwp.com<\/p>\n\n<h3>Source code<\/h3>\n\n<p>The bundled ALTCHA widget is version 2.3.0, licensed under MIT.\nSource: https:\/\/github.com\/altcha-org\/altcha\/tree\/v2.3.0\nBuild instructions are in that project's README and package.json.\nThe widget runs locally in the browser; no ALTCHA service account is required.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>If replacing SecurityWP, deactivate it first. Existing settings are retained.\nUpload the INI Protector ZIP through Plugins \u2192 Add New \u2192 Upload Plugin,\nthen activate <strong>INI Protector<\/strong>. Do not activate both versions together.<\/li>\n<li>Open <strong>INI Protector<\/strong> and enable the hardening you want. Each toggle is\nindependent and reversible.<\/li>\n<li>For \"Mask login URL\", set a slug before enabling so you can't lock yourself\nout.<\/li>\n<\/ol>\n\n<!--section=changelog-->\n<h4>1.9.5<\/h4>\n\n<ul>\n<li>Fixed PHP warnings and warning text appearing in the username field when login masking is enabled. The masked login loader now shares WordPress login globals with the login header, footer and authentication hooks, including interim-login state.<\/li>\n<\/ul>\n\n<h4>1.9.4<\/h4>\n\n<ul>\n<li>Traffic history now defaults to 90 days. Existing saved retention choices are preserved.<\/li>\n<li>Unlimited history shows a readable orange warning beside Configure, with a link to Utilities.<\/li>\n<li>Added Utilities \u2192 Clean traffic table contents, with administrator permission checks and a confirmation before permanently deleting history. Existing IP blocks and settings are kept.<\/li>\n<li>Traffic cleanup now reports database failures instead of displaying a success message.<\/li>\n<\/ul>\n\n<h4>1.9.3<\/h4>\n\n<ul>\n<li>Added a Check for updates text link on the Plugins screen to discover the latest WordPress.org directory release without waiting for the normal update-check cache.<\/li>\n<li>Added an explicit Update now action using the official WordPress.org ZIP and native WordPress replacement installer, with version, package and compatibility checks.<\/li>\n<li>Preserved plugin settings and activation during manual updates.<\/li>\n<\/ul>\n\n<h4>1.9.2<\/h4>\n\n<ul>\n<li>Author URLs now use independent random tokens that survive WordPress salt changes. Existing salt-derived author URLs are retired during upgrade; regenerate links and clear page caches.<\/li>\n<li>Explain recovery-code login when a salt change makes the existing two-factor setup unreadable. After signing in, reset and re-enroll two-factor authentication.<\/li>\n<\/ul>\n\n<h4>1.9.1<\/h4>\n\n<ul>\n<li>Asset cache tokens now use independent randomness; existing stored legacy salts are replaced on upgrade, even if masking is disabled.<\/li>\n<li>Hardened configuration, HTTP metadata and IP\/CIDR validation, and escaped generated email-link labels.<\/li>\n<li>Added explicit nonce verification to authentication forms and administrative save handlers.<\/li>\n<li>Enqueued admin, password gate and two-factor styles\/scripts through WordPress APIs.<\/li>\n<li>Limited dismissible scan notices to the dashboard and INI Protector pages.<\/li>\n<li>The email shortcode is now [inipr_obfuscate]. Replace any existing [obfuscate] shortcodes with [inipr_obfuscate].<\/li>\n<\/ul>\n\n<h4>1.9.0<\/h4>\n\n<ul>\n<li>Renamed the plugin to INI Protector with the ini-protector translation domain.<\/li>\n<li>Removed the self-hosted updater for WordPress.org distribution.<\/li>\n<li>Preserved existing settings, cron hooks, database tables and integration identifiers.<\/li>\n<li>Removed the site URL from vulnerability lookup requests and documented external services.<\/li>\n<\/ul>\n\n<h4>1.8.2<\/h4>\n\n<ul>\n<li><strong>The plugin can now be translated.<\/strong> It was written to be translatable throughout, but shipped no translation template and pointed at a language folder that did not exist \u2014 so no translation could ever load. A template covering all 341 strings is now included, and the folder it needs comes with it.<\/li>\n<li><strong>Fix: four messages could never be translated even once that was in place<\/strong>, because they were not tagged with the plugin's name internally. Three are the \"please complete the verification challenge\" errors shown on the login, registration and lost-password forms; the fourth is the message shown when feeds are disabled. They now appear in the translation template with everything else.<\/li>\n<li><strong>Fix: a request to <code>?author[]=1<\/code> wrote a PHP warning into the site's error log.<\/strong> Anyone could trigger it, and on a site configured to display errors it appeared on the page. Such a request is still treated as an author-enumeration probe and still returns 404 \u2014 it just no longer produces the warning.<\/li>\n<li>Server detection for Apache\/LiteSpeed now handles an unusual server name correctly.<\/li>\n<li>Housekeeping for the official Plugin Check tool: added the translator notes that explain what each number in a message refers to, and corrected suppression comments that were silencing nothing. No behaviour change.<\/li>\n<\/ul>\n\n<h4>1.8.1<\/h4>\n\n<ul>\n<li><strong>Fix: on sites not set to UTC, the traffic log's time windows were wrong by the site's time offset.<\/strong> A \"last 24 hours\" view actually showed the last 27 on a UTC+3 site, and every other window and the per-IP drill-down were stretched the same way. Requests are recorded in your site's own local time but were being compared against a UTC cut-off.<\/li>\n<li><strong>The same fix corrects pruning.<\/strong> If you had set a retention window, requests were kept for your chosen period <em>plus<\/em> the time offset before being removed \u2014 a 7-day setting really kept 7 days and 3 hours. Nothing was lost, it was held slightly too long.<\/li>\n<li>Windows are now also correct across daylight-saving changes, rather than being an hour out for part of the year.<\/li>\n<li>Sites set to UTC were never affected, and no stored data needs correcting \u2014 only the comparisons were wrong, never what was written.<\/li>\n<\/ul>\n\n<h4>1.8.0<\/h4>\n\n<ul>\n<li><strong>Traffic history is now kept indefinitely by default.<\/strong> 1.7.0 made the limits adjustable but still shipped the old 14-day window and 50,000-request cap; both are now off out of the box, so nothing is deleted unless you ask for it.<\/li>\n<li><strong>After updating, sites that never chose a retention window will start keeping traffic history indefinitely.<\/strong> Nothing already recorded is removed and nothing on the site slows down \u2014 requests are logged after the page has been sent \u2014 but the log will grow with your traffic, and it is included in database backups. The Traffic page shows what it currently costs and projects the growth; if that matters on your hosting, pick a window there.<\/li>\n<li><strong>The two settings are dropdowns instead of typed numbers.<\/strong> Retention offers Forever, 7, 14, 30, 90 and 180 days, 1 year and 2 years; the request ceiling offers No limit, and 10,000 up to 1,000,000. You can no longer mistype a value that means something you did not intend, and \"Forever\" is now stated plainly rather than expressed as a 0.<\/li>\n<li>Note the two settings work together: the request ceiling still overrides the window, so leaving a low ceiling in place will cut a long retention window short. Both now default to no limit, so this only applies if you set one.<\/li>\n<li>Hardening: a dropdown that receives a value not in its own list now falls back to that setting's normal value instead of storing it.<\/li>\n<\/ul>\n\n<h4>1.7.0<\/h4>\n\n<ul>\n<li>New: <strong>you choose how long traffic history is kept.<\/strong> The traffic monitor used to keep the last 14 days and at most 50,000 requests, with no way to change either. Both are now settings, and both can be switched off \u2014 set the retention to <strong>0<\/strong> to keep history indefinitely, and the row limit to <strong>0<\/strong> to remove the ceiling.<\/li>\n<li>New: a <strong>Retention settings<\/strong> window on the Traffic screen, next to a new line showing what the log currently costs \u2014 how many requests are stored, how much space they take, how far back they go, and the limits in force. The same settings remain available under Configure on the main settings page.<\/li>\n<li>Worth knowing before you switch retention off: keeping history costs <strong>disk space and backup size, not page speed<\/strong>. Requests are recorded after the page has already been sent to the visitor, and with both limits off the housekeeping step stops running altogether. The window shows the projected growth for your own site so the decision is an informed one. Bear in mind visitor IP addresses are personal data, so keeping them forever is a deliberate choice.<\/li>\n<li>The row limit is the one that usually runs out first: on a busy site 50,000 requests can be less than a day. If you want a longer history, raise or switch off <strong>both<\/strong> settings \u2014 the retention window alone will not do it.<\/li>\n<li>The Traffic screen now offers 30-day, 90-day and 1-year views, but only as far back as your retention setting actually keeps. Reading is capped at a year so a very wide view cannot bog the page down.<\/li>\n<li>Fix: typing something that is not a number into a settings box was read as 0. On the new settings that would have quietly meant \"no limit\", so a mistyped entry now falls back to the setting's normal value instead.<\/li>\n<\/ul>\n\n<h4>1.6.1<\/h4>\n\n<ul>\n<li>Fix: <strong>\"View version details\" could show \"Plugin not found\" instead of this changelog.<\/strong> The link on the Plugins screen opens a details window; when it could not be answered from the update server, the request was handed on to the WordPress.org plugin directory, which has never heard of a self-hosted plugin and answers with an error. Nothing was actually wrong with the plugin or the update \u2014 but that is not what it looked like.<\/li>\n<li>The details window now <strong>always answers<\/strong>, and falls back to the description and changelog bundled with the installed copy when the update server cannot be reached. That covers a real outage and also the ten-minute pause after a single failed check, which is why the plugin row could correctly offer a new version while the details window errored \u2014 two separate caches on two separate schedules.<\/li>\n<li>Fix: <strong>another plugin could overwrite our answer.<\/strong> The hook that supplies these details is a filter, not a final word: WordPress keeps running every other plugin's callback afterwards, and one that was written without checking <em>which<\/em> plugin was asked about can discard a perfectly good result. (Elementor ships exactly such a callback, and its error text is the \"Plugin not found.\" people were seeing.) The details are now re-asserted at the very end of that chain \u2014 only for this plugin, and only when what is on its way out is not already a usable answer, so no other plugin's details are affected.<\/li>\n<li>No change to how updates are downloaded or installed; this only affects the details window.<\/li>\n<\/ul>\n\n<h4>1.6.0<\/h4>\n\n<ul>\n<li>New: <strong>Rotate asset cache token<\/strong> \u2014 a way to change the masked <code>?ver=<\/code> on every CSS and JS URL at once, forcing a clean re-fetch site-wide. Available from the new <strong>INI Protector \u2192 Utilities<\/strong> page and from WP-CLI.<\/li>\n<li>Why this exists: the masked token is derived from the version an asset <strong>declares<\/strong>, so it is only as fresh as that version. Plenty of real-world code enqueues a script with a hard-coded string that never changes \u2014 the file behind it is updated, the declared version is not, the URL stays the same, and every returning visitor keeps running the copy in their browser cache. Rotating is the blunt instrument for that case, and for anything else where a deploy is live on the server but not in front of visitors.<\/li>\n<li>New: <strong>WP-CLI<\/strong> \u2014 <code>wp secwp asset-salt rotate<\/code> and <code>wp secwp asset-salt show<\/code>. <code>rotate<\/code> runs without a confirmation prompt so it can sit at the end of a deploy script, after the step that syncs changed files; <code>--porcelain<\/code> prints just the new token. <code>show<\/code> reports the current token, whether masking is actually active, and who last rotated it.<\/li>\n<li>Rotating <strong>costs a re-download<\/strong>: every visitor fetches all CSS and JS once more. That is fine after a deploy and wasteful as a habit, so the cost is stated at both the button and the command rather than left to be discovered.<\/li>\n<li>Every rotation records <strong>who did it, when, and from where<\/strong> (admin or WP-CLI), kept as a rolling history of the last ten on the Utilities page \u2014 so a spike in cache-miss traffic can be explained rather than guessed at.<\/li>\n<li>Rotating while version masking is <strong>off<\/strong> is reported as such instead of as success: with the mask off, or with \"Remove version on static assets\" on, there is no token for visitors to see and the rotation changes nothing they receive.<\/li>\n<li>The token shown in the admin and by WP-CLI is a short fingerprint of the salt, never the salt itself \u2014 publishing the salt would make every masked token reversible to a real version number again, which is the whole point of masking.<\/li>\n<li>New: a <code>secwp_asset_salt_rotated<\/code> action fires on every rotation. If the site sits behind a page cache, purge it from there \u2014 cached HTML still carries the old asset URLs, so until it is purged the rotation is invisible.<\/li>\n<li>New: the platform state endpoint emits an <code>asset_salt_rotated<\/code> event for the INI WP control panel. Additive (schema_version unchanged); no connector\/signing change.<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<ul>\n<li>New: <strong>File integrity monitoring<\/strong> (Security). INI Protector hashes every code file on the site (SHA-256), stores a baseline, and re-checks it on a schedule \u2014 reporting every file that is <strong>new<\/strong>, <strong>modified<\/strong>, or <strong>deleted<\/strong>. Media is excluded, so a large uploads folder costs nothing: a 24,000-file site hashes in about two seconds.<\/li>\n<li>The alert <strong>leaves the server before the baseline is updated<\/strong>. A monitor whose only record lives on the machine an attacker controls can be rewritten to match whatever they planted; an email or webhook that has already been sent cannot. If every configured channel fails, the baseline is deliberately left alone so the change is reported again next run instead of being silently accepted.<\/li>\n<li>Every report carries a <strong>sequence number<\/strong> and a <strong>chain of state digests<\/strong>, so a suppressed report (a gap in the numbering) or a doctored baseline (a digest that does not match the previous message) is detectable from your mailbox alone, without trusting anything on the site.<\/li>\n<li>Changes in <strong>critical locations<\/strong> \u2014 WordPress core, wp-config.php, mu-plugins, .htaccess, theme function files, the web root \u2014 lead the report and are flagged in the admin. A file whose contents changed while its timestamp did not move is called out separately: that is a self-rewriting payload or a forged mtime, not an ordinary edit.<\/li>\n<li>New: a <strong>INI Protector \u2192 File integrity<\/strong> page \u2014 the changes from the last check, a rolling history, \"Scan now\", \"Accept current state as the baseline\", and a dashboard alert. It also states plainly when no alert channel is configured, or when WP-Cron is disabled, and prints the exact system-cron line to use instead.<\/li>\n<li>New: <strong>WP-CLI<\/strong> \u2014 <code>wp secwp integrity scan|baseline|status|list<\/code>. <code>scan<\/code> exits with status 1 when changes are found and 0 when clean, so system cron and monitoring can act on it directly; <code>--format=json<\/code> for machine-readable output.<\/li>\n<li>New: <strong>Two-factor authentication<\/strong> (Security). RFC 6238 TOTP with any standard authenticator app, enforceable per role. The password is checked first and the second factor is required <strong>before a session cookie is issued<\/strong> \u2014 not by clearing one that was already set.<\/li>\n<li>Two-factor: ten single-use <strong>recovery codes<\/strong> are issued at setup and can be regenerated at any time, and <code>wp secwp 2fa reset &lt;user&gt;<\/code> removes 2FA from the shell \u2014 the break-glass for a lost phone. <code>wp secwp 2fa status<\/code> shows who is covered, and the Users list gains a 2FA column.<\/li>\n<li>Two-factor: the enrolment QR code is generated <strong>on this site<\/strong> \u2014 the setup URI contains the shared secret and is never handed to an external QR service. TOTP secrets are encrypted at rest with a key derived from your wp-config salts, so a database-only compromise cannot mint codes.<\/li>\n<li>Two-factor: password authentication over XML-RPC or REST is <strong>refused<\/strong> for enrolled accounts, since a second factor cannot be prompted for there \u2014 otherwise 2FA would be bypassable by pointing the same stolen password at xmlrpc.php. Application passwords keep working: they are per-application, revocable, and can only be created from inside an already-protected session.<\/li>\n<li>New: the platform state endpoint (secwp\/v1\/state) exposes read-only <code>file_integrity<\/code> (status, counts, digests, delivery result) and <code>two_factor<\/code> (required roles, enrolled count, accounts still missing it) blocks for the INI WP control panel, and emits <code>integrity_change<\/code>, <code>2fa_enabled<\/code>, <code>2fa_disabled<\/code> and <code>2fa_recovery_used<\/code> events. Additive (schema_version unchanged); no connector\/signing change.<\/li>\n<\/ul>\n\n<h4>1.4.2<\/h4>\n\n<ul>\n<li>Fix: the vulnerability alert's <strong>\"Dismiss until something changes\"<\/strong> button now keeps its promise \u2014 the alert stays hidden across the daily re-scans as long as they keep finding the <strong>same<\/strong> vulnerabilities, and reappears only when the finding set actually changes (a new vuln appears, or a fixed one drops off). In 1.4.1 the dismissal was keyed to the scan timestamp as well as the findings, so every daily re-scan brought the banner back even though nothing had changed.<\/li>\n<li>Compatibility: sites upgrading from 1.4.1 keep their existing dismissal \u2014 the old timestamped token is recognized, so the banner doesn't pop back once after the update.<\/li>\n<\/ul>\n\n<h4>1.4.1<\/h4>\n\n<ul>\n<li>Fix: the vulnerability-scan dashboard alert's <strong>Dismiss<\/strong> button now actually dismisses. The button is a nonce'd link, but the handler only read the action from POST data, so the click was ignored and the banner reappeared \u2014 it now reads the request regardless of method.<\/li>\n<li>Change: <strong>\"Dismiss until something changes\"<\/strong> now hides the vulnerability alert until the <strong>next scan runs<\/strong> (or the finding set changes), instead of staying hidden across re-scans as long as the same vulnerabilities were found. Dismissal is keyed to the scan timestamp + finding-set digest.<\/li>\n<li>Hardening: <strong>Disable XML-RPC<\/strong> now also hard-blocks a direct hit on xmlrpc.php with an early 403, before WordPress finishes loading \u2014 so bogus XML-RPC floods are cheap to absorb and scanners get a clean 403, not a 200\/405. The existing method-neutralizing filters (pingback.ping, system.multicall, etc.) stay in place as defense-in-depth. No server config required; fully portable.<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<ul>\n<li>New: a dedicated <strong>INI Protector \u2192 IP Block<\/strong> page \u2014 one home for blocking, separate from the Traffic monitor (which stays a read-only forensics view). It shows the system's suggested blocks alongside the IPs you've already blocked, with the manual add-IP box. The blocklist and add-IP box have moved here from the Traffic page.<\/li>\n<li>New: <strong>Suggested by the system<\/strong> \u2014 the traffic monitor's abuse-threshold candidates (the same ones the INI WP panel already saw) are now shown in the admin with a one-click <strong>Apply block<\/strong>. Previously these were computed but never surfaced in the UI.<\/li>\n<li>New: <strong>Auto-block escalation<\/strong> (Security toggle). Every 5 minutes the engine scores offending IPs and escalates <strong>temporary<\/strong> blocks on a ladder \u2014 1 hour \u2192 4 hours \u2192 8 hours \u2192 5 days \u2192 2 weeks. By default it only <strong>suggests<\/strong> (Block Suggestion System); turn on <strong>Enforce<\/strong> for fully automatic <strong>Auto-Block<\/strong> mode.<\/li>\n<li>New: temporary, expiring blocks. Blocks now carry an expiry and a source (manual \/ suggested \/ auto); manual blocks stay permanent, while suggested-Apply and auto blocks ride the escalation ladder and expire on their own. Clicking <strong>Apply<\/strong> on a suggestion now creates a temporary, escalating block (not a permanent one).<\/li>\n<li>Safety: an IP\/CIDR <strong>allowlist<\/strong> (IPv4 + IPv6) is never auto-blocked, and verified Googlebot\/Bing\/etc. (reverse + forward DNS) are always exempt \u2014 so the engine can't deindex your site or block your own ranges. It <strong>never<\/strong> issues a permanent block automatically, and an IP with no fresh offense for 30 days <strong>decays<\/strong> one level.<\/li>\n<li>New: the platform state endpoint (secwp\/v1\/state) exposes a read-only <code>autoblock<\/code> block (mode + escalation entries) and emits <code>autoblock_temp<\/code> events for the INI WP control panel. Additive (schema_version unchanged); no connector\/signing change.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>New: <strong>Vulnerability scan<\/strong> (Security). Once a day INI Protector checks every installed plugin, theme, and your WordPress core version against the free WPVulnerability database (CC0, no API key) and flags any component whose installed version is affected by a known vulnerability.<\/li>\n<li>New: a dedicated <strong>INI Protector \u2192 Vulnerabilities<\/strong> page lists each finding \u2014 component, type, installed version, severity, CVE reference, the version it's fixed in, and whether an update is already available \u2014 sorted worst-first, with a \"Scan now\" button.<\/li>\n<li>New: a <strong>dashboard alert<\/strong> + a count bubble on the menu when vulnerabilities are found. Dismiss it and it stays gone until the set of findings actually changes (a new vulnerability brings it back).<\/li>\n<li>New: <strong>opt-in email alert<\/strong> on new findings only (never on every scan), with a configurable minimum severity and recipient.<\/li>\n<li>New: the platform state endpoint (secwp\/v1\/state) exposes a read-only <code>vulnerabilities<\/code> block (counts + findings) for the INI WP control panel. Additive (schema_version unchanged).<\/li>\n<li>Privacy: no data about your site is sent \u2014 only the public slug of each component is looked up; lookups are cached and the daily scan is gated by the toggle.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>New: <strong>per-IP activity drill-down<\/strong> in INI Protector \u2192 Traffic. Click any IP in \"Top offender IPs\" to see a full profile \u2014 a clean\/watch\/suspicious\/hostile verdict with the signals behind it, status-code and reason breakdowns, top paths, user agents, and a request timeline \u2014 then Block or Unblock right from that page.<\/li>\n<li>New: a <strong>verdict badge<\/strong> on every IP in the Top-offenders list (clean \/ watch \/ suspicious \/ hostile), so risky IPs stand out at a glance. An expandable legend next to the day filters explains each level.<\/li>\n<li>New: the platform state endpoint (secwp\/v1\/state) exposes the same drill-down for the INI WP control panel \u2014 each top IP carries its verdict, and an optional ?detail_ip= returns the full per-IP profile. Additive (schema_version unchanged).<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>New: settings toggles now save via AJAX \u2014 flipping a switch no longer reloads the page.<\/li>\n<li>New: toast notifications confirm every change (enabled \/ disabled \/ saved) and surface errors, replacing the old reload-and-show-notice flow. No-JS browsers keep the classic notice.<\/li>\n<li>New: <strong>Mask version on static assets<\/strong> (Head cleanup) \u2014 replaces the real <code>?ver=<\/code> on CSS\/JS with an opaque, salted token. Hides the WordPress\/plugin version while keeping cache-busting (the token still changes when the real version does), so it's safer than fully removing <code>?ver=<\/code>.<\/li>\n<li>New: <strong>Disable oEmbed &amp; REST discovery<\/strong> (Head cleanup) \u2014 removes the REST API and oEmbed auto-discovery <code>&lt;link&gt;<\/code> tags, the <code>wp-json<\/code> HTTP Link header, and the version leak in oEmbed\/feed output. The REST API itself keeps working; no endpoint is disabled.<\/li>\n<\/ul>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Maintenance: no functional changes. Refreshes the self-hosted update manifest so the \"View details\" \u2192 Changelog window renders the full, formatted version history (matching SeoWP). Safe to install.<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>New: a built-in <strong>INI Protector \u2192 Traffic<\/strong> view for sites not on the INI WP platform \u2014 totals, top offender IPs, suspicious paths, recent suspicious requests, suggested blocks, and a 1h \/ 24h \/ 7d window selector. Renders the same data the platform pulls.<\/li>\n<li>New: <strong>IP blocking<\/strong> \u2014 block an offender (or any IP) straight from the Traffic view; blocked IPs get a 403 early in the request. Uses the spoof-resistant client IP, runs independently of the traffic monitor, and refuses to block your own current IP (no self-lockout). Admins always bypass.<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>New: the platform state endpoint (secwp\/v1\/state) now includes an aggregated <code>traffic<\/code> summary \u2014 totals, top offender IPs, suspicious paths, a recent sample, and suggested block rules \u2014 for the INI WP control panel's Traffic tab. Reports <code>enabled:false<\/code> when the traffic monitor is off. Honours an optional <code>?hours=<\/code> look-back window hint. Additive (schema_version unchanged).<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>New: \"Require login for REST API\" now has a per-namespace public whitelist \u2014 tick which registered REST namespaces stay public (e.g. a headless front-end, oEmbed, a contact-form endpoint); everything else requires login. Strict by default (nothing public until ticked).<\/li>\n<li>Safety: the INI WP family REST namespaces are always exempt and can never be blocked, so the control panel (wp.ini.bg) and integrating plugins can't be locked out. Family membership is read from the connector's registry (iniwp_family_rest_namespaces) when present, with a safe built-in fallback when standalone; INI Protector self-registers secwp\/v1. The whitelist UI shows the always-public namespaces read-only.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release. Security \/ head-cleanup \/ SEO-privacy hardening extracted\ninto a standalone plugin, plus the INI WP platform-state endpoint.<\/li>\n<\/ul>","raw_excerpt":"Lightweight WordPress hardening \u2014 file integrity monitoring, two-factor authentication, login protection, security headers and privacy.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/365481","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=365481"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/milenfrom"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=365481"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=365481"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=365481"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=365481"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=365481"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=365481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}