Changelog — JPKCom Hide Login

Version history of the JPKCom Hide Login WordPress plugin.

Version history of jpkcom-hide-login. Entries are taken verbatim from the project's README. A hands-on guide with tips is available in the project guide.

1.2.9

  • Changed: the update manifest generator now defaults a missing Network: header to false instead of true, matching WordPress' own default. No change for this plugin, which declares Network: true explicitly
  • CI: the lint and guard workflow now also runs on pushes to main. It only covered pull requests, so a direct push with bypass rights skipped every check
  • Changed: comments, workflow step names and CI output across the repository are now English throughout, and the developer notes in CLAUDE.md were translated and trimmed. No effect on the shipped plugin

1.2.8

  • Changed: Tested up to raised to WordPress 7.1
  • Changed: the bundled updater's runtime floor now matches the plugin's own minimum. It bailed out below WordPress 6.8 while the plugin header has required 6.9 for several releases, so the check could never fire on a supported installation
  • Docs: the remaining "WordPress 6.8" requirement statements now say 6.9, matching the plugin header
  • CI: the release manifest's fallback values for requires and tested now say 6.9 and 7.1. They only apply when the README metadata cannot be read, but a stale fallback would have published a minimum the plugin no longer supports

1.2.7

  • Fixed: a block renewed itself for as long as anyone kept trying. A rejected attempt still fired wp_login_failed, so the counter grew and block_ip() was called again with a fresh full duration — the "try again in N minutes" message was untrue and a locked-out administrator hitting reload never got back in. Attempts from an already blocked IP are no longer counted
  • Added: the settings screen now warns when the detected client address is a loopback, private or link-local address and no trusted proxy is configured — the case where every visitor shares one address (five wrong passwords lock out everybody) or, if that address is whitelisted, where the protection is silently off altogether. The notice names the JPKCOM_HIDE_LOGIN_TRUSTED_PROXIES line to add
  • Changed: the runtime check now requires WordPress 6.9, matching the Requires at least header, which demanded 6.9 while the check still let 6.8 through. Both are driven by JPKCOM_HIDE_LOGIN_MIN_WP / JPKCOM_HIDE_LOGIN_MIN_PHP so they cannot drift apart again
  • Changed: cleared every WordPress Plugin Check finding except plugin_updater_detected, which is inherent to a self-hosted updater — escaping at the point of output, prepared statements inline, wp_delete_file() instead of unlink(), documented nonce and sanitisation exemptions on the request-path parsing that must stay byte-exact
  • Removed: the manual load_plugin_textdomain() call. Since WordPress 6.7 the textdomain registry loads the plugin's own /languages just in time from the Domain Path header
  • Fixed: the shipped German .l10n.php translations stored plural entries as PHP arrays instead of a NUL-joined string. WordPress' translation controller passes that value straight to explode(), so on a German site the second failed login attempt died with a fatal TypeError — the login page returned HTTP 500 and no IP was ever blocked. Both files are regenerated with wp i18n make-php
  • Security: /wp-signup.php is now blocked on single sites too. Core answers it with a redirect to wp_registration_url(), which this plugin rewrites to the custom slug — so one anonymous request returned the secret login slug in the Location header
  • Fixed: the wp_login, wp_login_failed, authenticate, login_url, logout_url, lostpassword_url, logout_redirect and site_url callbacks required strictly typed arguments. Third-party code that fires these with fewer arguments or a null — MainWP Child calls do_action( 'wp_login', $user_login ) with one argument — caused a fatal ArgumentCountError / TypeError. All callbacks now accept the foreign shapes and normalise internally
  • Fixed: wp jpkcom-hide-login get-slug / set-slug did not exist; WP-CLI had registered them as get_slug / set_slug. Both are now declared with @subcommand, matching the documentation
  • Changed: verbose request tracing moved from WP_DEBUG to its own JPKCOM_HIDE_LOGIN_DEBUG constant (default off). It used to write ~10 lines per request on any WP_DEBUG site, including the request URI and the full $_GET of every login attempt — password-reset keys included

1.2.6

  • Changed: the plugin banners (assets/banner-1544x500.avif, assets/banner-772x250.avif) are now a plain #3c4955 surface with no lettering

1.2.5

  • Security: the client IP is now taken from REMOTE_ADDR; X-Forwarded-For, CF-Connecting-IP and X-Real-IP are only believed when the request arrives from a proxy declared via JPKCOM_HIDE_LOGIN_TRUSTED_PROXIES or the jpkcom_hide_login_trusted_proxies filter. Previously a single spoofed header made the plugin see a whitelisted address, which disabled both the wp-login.php block and the brute-force protection, and allowed an attacker to get someone else's address blocked
  • Security: the wp-login.php / wp-signup.php block no longer relies on a substring test against the raw request URI. //wp-login.php and /wp-%6cogin.php bypassed it; matching is now done against the script the server actually resolved, plus a normalised (decoded, slash-collapsed) path
  • Security: redirects only reveal the custom slug while the masked login page is being served or for an authenticated request — an anonymous probe no longer receives it in the Location header
  • Fixed: the wp-admin block matched any path containing "wp-admin", so a post with a slug such as /my-wp-admin-guide/ returned 404 for every visitor. It now matches the first path segment
  • Fixed: CIDR whitelist entries never matched IPv6 addresses because ip2long() returns false for them, while the admin UI happily accepted IPv6 ranges. Matching now works for both families via inet_pton(), and the prefix ceiling depends on the family (/128 vs /32)
  • Changed: IP keys are derived with a salted hash_hmac( 'sha256', … , wp_salt( 'auth' ) ) instead of a bare md5(), which was reversible for IPv4 in seconds. Existing attempt counters and blocks are invalidated once on upgrade
  • Added: tests/test-security.php — regression tests for every issue above, each written to fail against the previous implementation. Run in CI on every pull request

1.2.4

  • Security: update packages are now verified before installation — the verified file is handed to WordPress instead of being downloaded a second time, so the bytes that were checked are the bytes that get installed
  • Security: a missing or unfetchable SHA-256 checksum now aborts the update instead of installing unverified code (previously it silently skipped verification)
  • Security: pinned every GitHub Action to a full commit SHA and added Dependabot with a 7-day cooldown, so a moved tag can no longer change the release build
  • Security: tightened which download the updater claims, so sibling plugins cannot match each other's package
  • Fixed: sprintf() calls in the updater bound named arguments to a variadic parameter, which raises ArgumentCountError on PHP 8.3
  • Fixed: the "View Details" modal could fail with a TypeError when the manifest omitted requires_plugins
  • Performance: a failed manifest fetch is now cached for an hour instead of being retried on every admin request
  • Added: CI workflow on every pull request (PHP lint, named-argument check, YAML validation, action-pinning guard)

1.2.3 (2026-06-16)

  • Raised the minimum WordPress version to 6.9 and "Tested up to" to WordPress 7.0
  • Switched license metadata to the SPDX identifier GPL-2.0-or-later with the HTTPS license URI

1.2.2 (2026-06-16)

  • Security: updater prefers an exact match against the manifest download_url over the slug heuristic, so a tampered manifest can no longer bypass the checksum gate
  • Security: timing-safe checksum comparison (hash_equals()) with an is_string() guard against hash_file() failures
  • Security: manifest fetch via wp_safe_remote_get() (SSRF defense-in-depth)
  • Fixed PHP warning and missing contributor names in the plugin detail popup (display_name now provided)
  • Fixed PHP warning/deprecation on wp plugin list by completing the no_update transient entry (new_version, package, tested, requires_php)

1.2.1 (2026-03-13)

Fixed

  • ZIP-Upload Fix - Manual ZIP uploads via WP-Admin no longer fail with "invalid URL" error during checksum verification
  • Release ZIP Structure - GitHub Actions release now creates ZIP with top-level plugin folder, ensuring WordPress correctly recognizes updates

1.2.0 (2025-11-12)

Added

  • IP Whitelist Management - Add trusted IPs that will never be blocked
  • CIDR Range Support - Whitelist entire IP ranges (e.g., 192.168.1.0/24)
  • Enhanced Admin Interface - Professional settings page with all features
  • Blocked IP Viewer - See currently blocked IPs with expiration times
  • Brute Force Protection - Automatic IP blocking after failed login attempts
  • Customizable Protection Thresholds - Configure max attempts, attempt window, and block duration
  • Login Attempt Counter - Shows remaining attempts on failed logins
  • Current IP Display - See your current IP in admin settings
  • One-Click Block Clearing - Clear all blocked IPs with a button
  • Automatic Database Cleanup - Daily WordPress Cron job removes expired login attempt data
  • WP-CLI Cleanup Command - Manual database cleanup via wp jpkcom-hide-login cleanup
  • Full WP-CLI Support - Complete command-line management for all plugin features

1.0.0 (2024-11-30)

  • Initial Release
  • Basic login URL masking
  • Simple IP blocking
  • Multisite support
  • Basic admin settings