=== KeyTurn ===
Contributors: (placeholder)
Tags: security, salts, keys, rotation
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 0.1.8
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Rotates the WordPress authentication keys and salts on a schedule, safely.

== Description ==

The eight authentication constants (`AUTH_KEY`, `SECURE_AUTH_KEY`, `LOGGED_IN_KEY`, `NONCE_KEY` and their four `_SALT` counterparts) sign WordPress's auth cookies and nonces. KeyTurn rotates them on a schedule — weekly, monthly, or quarterly — and gives you a one-click "Rotate now" button for incidents.

**What rotation buys you:** stolen cookies get a bounded useful life, leaked constants go stale, and post-incident cleanup is one click. **What it costs:** every rotation signs *every* user out of the site — including the administrator who triggers it. KeyTurn tells you this before you commit, on a preview screen, and again in every warning. Plugins that encrypt stored secrets against the salts — SMTP plugins are the common case — will ask for those secrets again after each rotation (see the FAQ).

**Architecture, in two sentences.** KeyTurn edits wp-config.php exactly once, replacing the eight `define()` lines with a small marked block that loads a dedicated file, `keyturn-salts.php`, sitting beside wp-config.php. Every rotation thereafter rewrites only that one file — atomically — so wp-config.php is never regex-edited on a schedule.

**One outbound request, and you can see it.** About twice a day KeyTurn asks designbyroger.com whether a newer version exists — the same check WordPress makes to wordpress.org for directory plugins, sending the same things: your site URL, plugin, WordPress and PHP versions, and locale. Nothing else ever leaves your server: no telemetry, no salt values. Salts are generated locally with PHP's cryptographic random source. One bundled library (the update checker), no build step, no custom database tables. The one other HTTP request is a self-test that requests your *own* site's URL over loopback to confirm the salts file isn't being served as text — it contacts no external host.

**Requirements:** PHP 8.1+ and WordPress 6.2+. Single-site only for now; multisite is refused at activation and planned for a later release.

**Security contact:** report vulnerabilities privately to admin@designbyroger.com.

== Installation ==

1. Install and activate the plugin. Activation is side-effect-free: nothing is changed until you arm it.
2. Go to **Settings → KeyTurn**. Scroll to **Settings**, choose the schedule, how rotations run, and a notification email address, and save. Then choose **Arm KeyTurn** at the top of the page.
3. The Arm screen previews exactly what will happen — the one-time wp-config.php edit, the backup it takes first, the fact that everyone (including you) will be signed out, and the security-scanner heads-up — before you confirm.
4. Confirm. You'll be signed out and sent to the login screen; sign back in.

After arming, KeyTurn rotates on your chosen schedule. You can rotate manually at any time, and restore wp-config.php to its original shape (so the plugin can be deleted safely) from the same settings page.

== Frequently Asked Questions ==

= My security plugin flagged keyturn-salts.php =

That is expected and correct. `keyturn-salts.php` is a legitimate, non-core PHP file sitting beside wp-config.php, and there is no registry that lets a plugin pre-clear itself with scanners — so a scanner will notice it. What to do depends on the scanner:

* **Wordfence** flags it as an unknown file (a warning for review, never auto-deletion). It offers "ignore until the file changes" and "always ignore". **Choose *always ignore*** — because rotation rewrites the file, "ignore until it changes" will re-warn after every rotation, forever.
* **File-change detectors** (Solid Security and similar) report the one-time wp-config.php edit and every rotation as change notices. Exclude `keyturn-salts.php` from change tracking.
* **Host-level heuristic scanners** (Imunify360 and similar) generally leave it alone — it is eight guarded `define()`s under a plain, self-describing header, which is about the most boring PHP a heuristic can meet.

The file's header comment says, in plain language, exactly what it is and which plugin manages it, so anyone reviewing a scanner flag understands it in seconds.

= Why was everyone logged out? =

Because that is what rotating the keys and salts *does* — they sign every auth cookie and "remember me" token on the site, so changing them invalidates every existing session at once. This is the point of the feature: after a rotation, no previously-issued cookie works. Everyone, including you, simply signs in again.

= My SMTP (or other) plugin asked for its credentials again after a rotation =

Expected, and not a fault in either plugin. Some plugins encrypt secrets they store — an SMTP password is the common case — using WordPress's own keys and salts as the encryption key. Rotate the salts and those secrets can no longer be decrypted, so the plugin asks you to enter them again. FluentSMTP does this; others in the same class behave the same way.

The practical consequence: after every rotation, scheduled ones included, re-enter those credentials. Before you arm KeyTurn, know which of your plugins store encrypted secrets, and keep those secrets somewhere you can reach *after* you've been signed out — a rotation on a site whose only mail path is now broken is inconvenient, not dangerous, but it is avoidable.

If your mailer can keep its credentials in wp-config.php instead of the database (FluentSMTP calls this "Store Access Keys in Config File"), use that mode: the credential is then outside the salts entirely and rotations never touch it.

= I use nginx =

nginx does not read `.htaccess`, so KeyTurn cannot write a deny rule for you. You don't strictly need one: the salts file is pure PHP with no output, so a direct request executes it and returns an empty body — the same protection wp-config.php itself relies on. If you'd like an explicit deny anyway, add this to your server block:

`location = /keyturn-salts.php { return 404; }`

The **Salts file protection** check on the settings page confirms the result.

(If wp-config.php lives above your web root, the salts file follows it there and has no public URL at all — nothing further is needed.)

= How do I run rotations from my host's cron instead of WP-Cron? =

On the settings page, set **How rotations run** to *System cron*. KeyTurn shows a ready-to-paste crontab line built for your chosen frequency. It is a plain `php` invocation of a small runner shipped with the plugin. In a crontab, put a `MAILTO` line above it:

`MAILTO=you@example.com`
`<minute> <hour> <day> * * php /path/to/wp-content/plugins/keyturn/keyturn-cron.php`

The runner finds WordPress itself — there is no `cd`, no `wp`, and no `--path` to get wrong. It is inert to the web (a direct HTTP request returns an empty 404); only a real command-line run can rotate. The `MAILTO` line makes cron email you the runner's output — and the runner prints only on failure, so a rotation that could not run reaches you even if your site's own mail is down. The settings page shows the exact line for your site.

One thing to check: the `php` that cron finds is not always the version your site runs on. From a shell as the cron user, run `which php` and `php -v`; if that version differs from the one shown under Tools → Site Health → Info → Server, replace `php` in the line with the absolute path of the right binary (for example `/usr/bin/php8.3`). Hosts that offer several PHP versions usually document where each lives.

Where to paste it: a host cron panel that accepts a command, or your user crontab over SSH (`crontab -e`). Some panels accept only a URL to fetch — that cannot run this line; use the crontab instead. Either way, run the line once by hand before relying on it.

If you administer the site over SSH and have WP-CLI, the equivalent command is:

`wp keyturn rotate --quiet --path=<path to your WordPress root>`

If scheduled rotations stop happening, KeyTurn tells you on the settings page and by email (when a notification address is set).

= My site was migrated and scheduled rotations stopped =

If you run rotations from system cron, the crontab line contains the absolute path to `keyturn-cron.php`. A hosting migration — yours or your host's — that moves the site's document root leaves that path pointing at nothing, and cron fails silently. KeyTurn will tell you the rotation is overdue on the settings page (and by email, when an address is set), but only after it is already late.

After any migration, run `crontab -l` and check the path against where the plugin actually lives now. Then run the line once by hand to confirm.

= What if I delete the plugin without restoring first? =

The supported way to remove KeyTurn is the **Restore & prepare for removal** button, which puts the keys and salts back into wp-config.php first. If you delete the plugin without doing that, KeyTurn's uninstaller attempts the same restore automatically. It runs with no user interface and cannot ask questions, so it follows one rule: the salts file is deleted only after the wp-config.php restore has been written and verified. It also sweeps up any backup files it took at arming, and removes all of its settings. The worst case of a careless delete is therefore a working site that still loads its salts from `keyturn-salts.php` through the block in wp-config.php, with nothing left managing them — never an outage. To finish the job by hand, move the eight `define()` lines from `keyturn-salts.php` back into wp-config.php in place of the block, then delete the salts file — and remove the KeyTurn line from your crontab if you were using system cron.

= What should I do if I suspect a break-in? =

Order matters: rotating while an attacker still holds FTP/SFTP or database credentials is futile — they simply read the new salts or write themselves back in. Revoking that infrastructure access first is what makes everything after it stick.

1. Change your infrastructure secrets — the keys and passwords that live outside WordPress — at the host: control panel, SFTP, database user password, any API keys stored in the site (mail, payments, maps).
2. Rotate with KeyTurn — this immediately signs out every session on the site, including yours; sign back in and continue.
3. Reset every user password directly — in the admin screens (Users → edit → Set New Password), or with WP-CLI (WordPress's command-line tool) if you have server access — rather than by email; assume the mail path is compromised until proven otherwise. Start with administrator accounts. Note: force-change-at-next-login is not part of WordPress core; direct resets are the reliable path.
4. Audit and purge users: look for administrator accounts created during the breach and remove them.
5. Hunt for persistence: unfamiliar files, modified plugins, must-use plugins, unknown scheduled tasks, and new access keys or FTP accounts at the host. Restore from a known-clean backup if in doubt.
6. Rotate with KeyTurn again — salts generated while the attacker may still have had file access are assumed read; the final rotation closes the loop.

Rotation ends every session; it does not change any passwords. An attacker who knows a valid password can sign straight back in — rotate, then reset passwords. KeyTurn is one step of incident response, not the whole response.

= Does KeyTurn phone home? =

Only to check for updates. KeyTurn is distributed from designbyroger.com rather than the WordPress.org directory, so about every twelve hours it asks `https://designbyroger.com/plugins/keyturn.json` for the current version — exactly as WordPress asks wordpress.org about directory plugins, and carrying the same fields: your site URL, the plugin, WordPress and PHP versions, and locale. Nothing else: no salt values, no telemetry, no other host. Salts are generated locally with PHP's cryptographic random source. The only other HTTP request it ever makes is the optional "salts file protection" self-test, which requests your own site's URL (`keyturn-salts.php`) over loopback to confirm your server isn't serving the file as readable text.

== Changelog ==

= 0.1.8 =
* Updates now come from designbyroger.com: the plugin carries Plugin Update Checker 5.7 and checks https://designbyroger.com/plugins/keyturn.json about twice a day. This is KeyTurn's only outbound request; the readme says exactly what it sends.
* Tested up to 7.1; security contact added to the readme.

= 0.1.7 =
* The arming backup of wp-config.php is now removed on the first verified admin page load after arming, instead of at the first rotation — no more weeks of file-scanner alerts on a file with no remaining value.
* Readme: mailers with a store-credentials-in-wp-config mode sidestep the re-enter-after-rotation step.

= 0.1.6 =
* Wording pass across readme, settings page, arm preview, notices and emails: encrypted-secrets (SMTP) consequence stated at every commitment point; crontab-removal reminders on restore; overdue guidance names the crontab path and php binary; careless-delete FAQ corrected and given a hand-finish; nginx snippet corrected.
* Fix: rotation email no longer reports a WP-Cron "next due" date when rotations run from system cron.

= 0.1.5 =
* Readme: known interactions — plugins that encrypt secrets against the salts (SMTP), host migrations breaking crontab paths; cron FAQ: which-php check, MAILTO, URL-only panels.
* Settings: crontab example shown whenever a schedule is set, not only after System cron is saved; PHP-binary and MAILTO note.

= 0.1.4 =
* Documentation: changelog brought current.

= 0.1.3 =
* Incident panel attention treatment: red warning heading with dashicon, bold preamble.

= 0.1.2 =
* Recovery instructions rewritten: ordered sequence, plain-language secrets, double-rotation rationale; incident panel mirrored.

= 0.1.1 =
* Versioning: plain patch bumps, dev-suffix dropped.

= 0.1.0 =
* Initial release.
