Title: Kwistech Secure
Author: kwistech
Published: <strong>August 26, 2026</strong>
Last modified: August 26, 2026

---

Search plugins

![](https://ps.w.org/kwistech-secure/assets/banner-772x250.png?rev=3667588)

![](https://ps.w.org/kwistech-secure/assets/icon-256x256.png?rev=3667588)

# Kwistech Secure

 By [kwistech](https://profiles.wordpress.org/kwistech/)

[Download](https://downloads.wordpress.org/plugin/kwistech-secure.1.6.0.zip)

 * [Details](https://wordpress.org/plugins/kwistech-secure/#description)
 * [Reviews](https://wordpress.org/plugins/kwistech-secure/#reviews)
 *  [Installation](https://wordpress.org/plugins/kwistech-secure/#installation)
 * [Development](https://wordpress.org/plugins/kwistech-secure/#developers)

 [Support](https://wordpress.org/support/plugin/kwistech-secure/)

## Description

Kwistech Secure hardens a WordPress site using only what WordPress already gives
you. It is
 not a firewall, not a malware scanner, and not a service — everything
it does happens on your own server, and it never contacts anybody.

**Twelve controls, each independently switchable, each logged when it changes:**

 * **Two-factor sign-in** — RFC 6238 authenticator codes with one-time recovery 
   codes. Detects
    an existing 2FA plugin and stays out of the way rather than fighting
   it.
 * **Login rate limiting** — counts failures by IP address _and_ by username, so
   a password
    sprayed across many accounts from one host and one account attacked
   from a botnet are both caught. Usernames are stored only as a salted hash.
 * **Non-enumerating login errors** — one identical message for every failure, on
   the sign-in
    form and the password-reset form alike, so neither confirms which
   usernames exist.
 * **Custom sign-in address** — moves `/wp-login.php`. Tests the new address before
   saving it
    and provides a break-glass constant, because the obvious way to implement
   this is also the obvious way to lock yourself out.
 * **File editor disabled** — removes the most direct path from a stolen administrator
   session
    to arbitrary code execution.
 * **XML-RPC off** — closes a long-standing brute-force amplifier that most sites
   never use.
 * **Application password inventory** — reports how many exist per user, never their
   values.
    They bypass two-factor, and most people have forgotten the ones they
   created.
 * **Version fingerprints removed** — generator tags, feed generators, `X-Powered-
   By`.
 * **Username discovery blocked** — `?author=1` scans, the REST user list, the user
   sitemap
    and oEmbed author fields all stop answering anonymous callers. Author
   archives at their own address keep working, and so does everything a signed-in
   editor needs. The control also reports when an account’s display name _is_ its
   username, because when that is true the byline gives the game away regardless.
 * **Security response headers** — `X-Content-Type-Options`, `X-Frame-Options`,
   
   Referrer-Policy and `X-Permitted-Cross-Domain-Policies`, and never overwriting
   one your server or CDN already sends. Deliberately no CSP: any policy that leaves
   wp-admin usable needs `unsafe-inline`, which does not stop the attack CSP exists
   to stop.
 * **Enforced password policy** — a minimum length, and a refusal of passwords containing
   the
    username or the site name, applied server-side when a password is set or
   reset. WordPress itself only shows a strength meter next to a “confirm use of
   weak password” checkbox.
 * **`unfiltered_html` restricted** — via a capability filter rather than the all-
   or-nothing
    constant, so administrators keep the workflows they need.

**An append-only activity log** records every setting change with the old value,
the new
 value, who made it, from what address, and when. There is no edit path 
and no single-row delete; the only removal is the scheduled retention sweep, which
logs itself.

#### What it does not do

Being clear about this matters more than the feature list:

 * It does not scan for malware, and a clean install proves nothing about whether
   you are
    infected.
 * It does not filter traffic. It is not a WAF and cannot stop an attack in progress.
 * It does not patch anything. It reports on outdated core and plugins; updating
   is yours.
 * It cannot protect against someone who already has your administrator password
   _and_ your
    second factor.

#### Privacy

This plugin makes **no third-party network request of any kind**. There is no account,
no
 registration, no telemetry, no usage reporting, no licence check, and no remote
access. It does not phone home because there is nowhere for it to phone.

Two requests are made, both to your own website address: an HTTPS request to your
home page
 to check whether a security header is present, and a one-off request 
to a new sign-in address to confirm it works before saving it. Neither carries anything
about you.

Data stored, all locally: the activity log (400 days), failed sign-in attempts with
usernames
 hashed (30 days), and encrypted two-factor secrets in user meta until
you turn the feature off. Deleting the plugin removes all of it.

#### Optional paid add-on

A separate plugin, **Kwistech Secure — Insurance Evidence Pack**, generates a dated
PDF and
 JSON report of this site’s security posture for an insurance broker. It
is sold at [kwistech.ca](https://kwistech.ca/kwistech-secure) and is not required
for anything here.

Everything described on this page is free, works without it, and always will. This
plugin
 contains no locked functionality.

## Screenshots

[[

[[

[[

## Installation

 1. Upload the plugin through **Plugins  Add New  Upload Plugin**, or install it from
    the directory.
 2. Activate it. Ten of the twelve controls switch on immediately at safe defaults.
 3. Open **Kwistech Secure  Hardening** and decide on the two that need a decision 
    from you: the custom sign-in address and two-factor enforcement. Both default to
    off, deliberately — enabling either without preparation can lock you out of your
    own site.

No configuration file to edit, no keys to obtain, no account to create.

## FAQ

### Will this lock me out?

Two controls can, and both are built to make that hard. The custom sign-in address
makes a
 real request to the new address before saving it, and refuses to save if
it does not answer; if the check cannot run, you must tick an acknowledgement, and
that choice is recorded. Two-factor enforcement cannot be switched on until at least
one administrator has enrolled, because requiring codes before anyone has an authenticator
locks out everybody at once.

If you are locked out anyway, add `define( 'KWISTECH_SECURE_DISABLE_LOGIN_SLUG',
true );` to
 wp-config.php and `/wp-login.php` works again immediately.

### Does it conflict with my existing security plugin?

It detects other two-factor plugins and steps aside rather than running a second

implementation. For firewall and scanning plugins there is no overlap — this one
does neither. Switch off any individual control that duplicates something you already
have.

### Does it slow the site down?

The hardening controls are filters and capability checks with no database work on
a normal
 page load. Nothing runs on the front end except the header changes.

### Does it work on multisite?

The code paths exist and have never been run on a real network. Treat multisite 
as
 unsupported rather than broken.

### What happens to my data if I uninstall it?

Deleting the plugin removes its options, both database tables, the scheduled job,
and every
 user meta key it set, including two-factor secrets and recovery codes.
Deactivating changes nothing. Constants you added to `wp-config.php` by hand are
left alone, because this plugin never edits that file.

### Is the source readable?

Yes, deliberately. No minified files, no bundled dependencies, no build step, no
obfuscation.
 A security plugin you cannot read is a security plugin you cannot 
check.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Kwistech Secure” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ kwistech ](https://profiles.wordpress.org/kwistech/)

[Translate “Kwistech Secure” into your language.](https://translate.wordpress.org/projects/wp-plugins/kwistech-secure)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/kwistech-secure/), 
check out the [SVN repository](https://plugins.svn.wordpress.org/kwistech-secure/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/kwistech-secure/)
by [RSS](https://plugins.trac.wordpress.org/log/kwistech-secure/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.6.0

 * Every option, user meta key, database table and scheduled job this plugin stores
   has moved
    from the `kws_` prefix to `kwistech_secure_`. Three characters is 
   below the four-character minimum the plugin directory asks of a prefix, and it
   was the one part of the plugin still using the short form. **Your data is migrated
   automatically** the first time an administrator loads a wp-admin page after upgrading:
   settings, audit log, login history, two-factor enrolment and recovery codes all
   carry across. Tables are renamed rather than copied, so no audit entry is lost.
 * If you read this plugin’s data directly — a WP-CLI script, a dashboard widget,
   a database
    query — update it to the new names. `kws_settings` is now `kwistech_secure_settings`,
   the audit table is now `{prefix}kwistech_secure_audit_log`, and the daily job
   is now kwistech_secure_daily_maintenance.
 * The key used to encrypt two-factor secrets at rest is rotated as part of the 
   same change.
    This is transparent: existing secrets are still read under the 
   previous key, so your authenticator app keeps working and there is nothing to
   re-enrol.
 * Removed the cleanup that deleted two retired database tables and a retired encrypted
   
   credential left over from a payment feature that was never released. No installation
   has ever held that data, so the code deleted something that could not exist.
 * The add-on notice introduced in 1.5.0 is now four lines instead of twelve, and
   appears only
    on this plugin’s dashboard rather than on all four of its screens.
 * Reading a submitted form field now requires that the capability and nonce check
   has already
    passed, enforced in code rather than by convention. No behaviour
   changes for anyone using the plugin normally; it closes the gap where a handler
   added in future could forget the check.
 * Requires the Insurance Evidence Pack add-on 1.2.0 or newer, if you use it. Older
   add-on
    versions keep working but will not have their own data migrated.

#### 1.5.0

 * Added a one-off notice on this plugin’s own screens introducing the Insurance
   Evidence Pack
    add-on. It appears nowhere else in wp-admin, never appears if 
   the add-on is installed, is dismissed permanently and per-user, is styled as 
   information rather than as a warning, and loads nothing from anywhere. Developers
   and agencies can switch it off with the kwistech_secure_show_addon_notice filter.
 * Fixed a test-isolation bug where the active-plugin cache leaked between unit 
   tests.

#### 1.4.0

 * **Three new hardening controls, taking the total to twelve.** All three default
   to on; none
    of them can lock you out.
    - _Block username discovery._ Closes `?author=N` scans, `/wp-json/wp/v2/users`,
      the user
       sitemap and oEmbed author fields to anonymous visitors. This was
      a known gap, recorded in the threat model as accepted residual R-5. Refusing
      to confirm usernames at the sign-in form while handing the list out over REST
      was never a coherent position.
    - _Security response headers._ Four of them, and it will not overwrite a header
      your server
       or CDN already sends.
    - _Enforced password policy._ Minimum length, and no passwords containing the
      username or
       the site name. Separators and case are ignored on both sides 
      of that comparison, so “Test-Site-2026” is caught for a site called “Test 
      Site”. Existing passwords are stored as hashes and cannot be assessed; the
      policy applies from the next time each one is set.
 * Added a regression test that the hardening form and its save handler agree on
   the field
    list. An unchecked checkbox submits nothing, so a field rendered but
   not read back is silently switched off on every save — a bug that only surfaces
   as “this setting will not stay on”, and precisely the mistake a three-control
   release invites.

#### 1.3.0

 * Renamed from “Kwistech WP Secure” to “Kwistech Secure”. “WP” is a restricted 
   term that
    cannot appear in a plugin name or slug, so the old name could never
   have been published. The plugin folder is now `kwistech-secure`; if you installed
   1.2.0 by hand, delete the old kwistech-wp-secure folder after upgrading or WordPress
   will run both copies at once. Settings, audit log and login history are untouched—
   they are keyed independently of the folder name and carry across.
 * Extension hooks renamed to match the new slug: `kws_dashboard_cards`,
    kws_settings_panels,`
   kws_admin_notices` and `kws_admin_notice_messages` become kwistech_secure_*. 
   The opt-out constant `KWS_DISABLE_LOGIN_SLUG` becomes KWISTECH_SECURE_DISABLE_LOGIN_SLUG.
   Update the add-on to 1.1.0 at the same time.
 * Now requires WordPress 6.2, up from 6.0. Table names in the audit log and login-
   attempt
    queries are passed through `$wpdb->prepare()` as `%i` identifier placeholders
   rather than interpolated into the SQL string. The old form was safe — the names
   were built from $wpdb->prefix plus a literal — but “safe because you can read
   the surrounding code” is weaker than “cannot be otherwise”, and it needed a static-
   analysis suppression on every query to say so. `%i` arrived in WordPress 6.2.

#### 1.2.0

 * Split into two plugins. The evidence pack is now a separate paid add-on distributed
   from
    kwistech.ca; this plugin contains no locked functionality and never will.
 * Added extension points so add-ons attach through documented hooks rather than
   patching.
 * Settings screen now explains what is stored and for how long, instead of offering
   a
    retention control that governed data this plugin no longer owns.
 * Renamed for clarity. The plugin folder and all settings are unchanged; nothing
   to migrate.

#### 1.1.0

 * Removed the payment layer entirely. No webhook, no outbound third-party request,
   no stored
    credential.
 * Every entry point is now a capability-checked, nonce-protected wp-admin request.

#### 1.0.1

 * Fixed admin styles being served from cache after an update.

#### 1.0.0

 * Initial release.

## Meta

 *  Version **1.6.0**
 *  Last updated **23 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.2 or higher **
 *  Tested up to **7.1**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [audit log](https://wordpress.org/plugins/tags/audit-log/)[hardening](https://wordpress.org/plugins/tags/hardening/)
   [login security](https://wordpress.org/plugins/tags/login-security/)[security](https://wordpress.org/plugins/tags/security/)
   [two factor](https://wordpress.org/plugins/tags/two-factor/)
 *  [Advanced View](https://wordpress.org/plugins/kwistech-secure/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/kwistech-secure/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/kwistech-secure/reviews/)

## Contributors

 *   [ kwistech ](https://profiles.wordpress.org/kwistech/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/kwistech-secure/)