# PHASE 14: SECURITY HARDENING AND FULL QA SWEEP

Read `docs/05-TECHNICAL-ARCHITECTURE.md` section 7, `CLAUDE.md` section 6, and `docs/08-QA-TEST-PLAN.md` in full.

## Scope

Harden, then test everything. This is the last phase before deployment.

**Server-side items run on staging**, which exists from phase 00 (decision D-24). Items 6, 7, 12, 13 and 14 need a real cPanel account and cannot be done against a local container. Repeat them against production during phase 15.

## Build

### Code audit

1. Walk every AJAX and REST endpoint in the plugin. Each must verify a nonce, check a specific capability, and whitelist its inputs by name. **No `permission_callback` returning `true`.** Fix anything that fails.
2. Grep for unescaped output. Every echo goes through the right escaping function for its context. `composer run lint` with `WordPress.Security.EscapeOutput` at severity 10 should be clean.
3. Grep for unprepared SQL. Every `$wpdb` call uses `prepare()`.
4. Confirm no credential, key or token exists anywhere in the repo. `git log -p | grep -iE "api[_-]?key|secret|password|token"` should return nothing meaningful.
5. Confirm no `get_post_meta` or `WP_Query` touches an order anywhere.

### Configuration

6. Apply the full `wp-config.php` constant set from technical architecture section 7, with fresh salts. **The table prefix was set in phase 00 and must not be changed now**: renaming tables at this point means renaming `wc_orders`, `wc_orders_meta`, `wc_order_addresses` and Action Scheduler's tables, plus rewriting the `{prefix}capabilities` and `{prefix}user_level` user meta and the `{prefix}user_roles` option, or every admin silently loses their role. Verify it is not `wp_` and move on.
7. Server, on staging: `Options -Indexes`, deny PHP execution in `/wp-content/uploads/`, block `xmlrpc.php`, `readme.html`, `.git`, `*.sql`, `*.log`. File permissions 644 and 755, `wp-config.php` 600. `disable_functions` is `PHP_INI_SYSTEM` and cannot be set from cPanel: raise a support ticket and record the answer, do not tick it off as done.
8. `BC_Login_Limiter`: transient-based, five attempts per IP per fifteen minutes, generic error messages.
9. Disable XML-RPC. Disable user enumeration: redirect `?author=1`, restrict `/wp/v2/users` to logged-in.
10. **Password policy** (brief section 34): enforce 12 characters with mixed character classes through `user_profile_update_errors` and `validate_password_reset`. Twenty lines, no plugin needed.
11. Install Two-Factor and enable it on every admin account. One Administrator, everyone else Shop Manager. Sessions expire after 8 hours.
12. Install UpdraftPlus. Database every 6 hours, files daily, retention 14 and 7, remote destination on a dedicated account, **never the same cPanel account**.
13. Weekly `mysqldump` cron to `/backups` outside `public_html`, gzipped, keep 4.

### Restore test

14. **Actually restore a backup** into a fresh staging database and run the ten-point verification from technical architecture section 8. Record the wall-clock time. That number is the recovery time to quote the client. If it is over 60 minutes, fix the process, not the number.

### Full QA

15. Execute `docs/08-QA-TEST-PLAN.md` in its entirety, every section A to F, on real devices. Record pass, fail or blocked per item.
16. Fix every failure. Re-test. Do not carry a known failure into deployment.

## Acceptance criteria

- [ ] Every endpoint audited, findings and fixes listed in the phase notes
- [ ] `composer run lint` clean at severity 10 for escaping, sanitisation, nonces and prepared SQL
- [ ] No secret in the repo or its history
- [ ] Every wp-config constant applied, salts regenerated, table prefix confirmed not `wp_`
- [ ] Password policy enforces on a new user and on a password reset
- [ ] Login limiter works, verified by triggering it
- [ ] XML-RPC returns 403, `?author=1` does not enumerate
- [ ] 2FA active on every admin account
- [ ] Backups running and visible at the remote destination
- [ ] **A restore actually completed and verified**, time recorded
- [ ] The full QA plan executed with results recorded, and every failure fixed and re-tested

## Stop

Do not deploy. That is phase 15, and it happens after this phase is signed off.
