Least-Privilege Role SOP
Most sites hand out Administrator like candy. Map roles to actual jobs.
— Step 1: List every user and current role. Anyone you can't name the job for gets demoted.
— Step 2: Content writers get Author, never Editor. Editors can't install or edit plugins by default — keep it that way.
— Step 3: Cap total Administrators at two humans, both yours.
— Step 4: Remove the
— Step 5: Audit custom roles from old plugins; deleted plugins leave orphaned capabilities behind.
— Step 6: Verify with a capability viewer that no role exceeds its mandate.
Do this on every new site before launch.
Run this every time.
Most sites hand out Administrator like candy. Map roles to actual jobs.
— Step 1: List every user and current role. Anyone you can't name the job for gets demoted.
— Step 2: Content writers get Author, never Editor. Editors can't install or edit plugins by default — keep it that way.
— Step 3: Cap total Administrators at two humans, both yours.
— Step 4: Remove the
edit_files capability site-wide via DISALLOW_FILE_EDIT — no theme/plugin editor in admin.— Step 5: Audit custom roles from old plugins; deleted plugins leave orphaned capabilities behind.
— Step 6: Verify with a capability viewer that no role exceeds its mandate.
Do this on every new site before launch.
Run this every time.
XML-RPC Shutdown SOP
Do this on every new WordPress site before launch.
— Step 1: Confirm exposure. Hit
— Step 2: Block at the edge, not in PHP. Add an Nginx
— Step 3: If a plugin (Jetpack, app login) needs it, whitelist Automattic IP ranges instead of opening it globally.
— Step 4: Kill pingbacks specifically. Filter
— Step 5: Verify. Re-request the file, expect 403. Check logs for system.multicall amplification attempts.
Run this every time.
Do this on every new WordPress site before launch.
— Step 1: Confirm exposure. Hit
/xmlrpc.php — a 405 with "only accepts POST" means it's live.— Step 2: Block at the edge, not in PHP. Add an Nginx
location = /xmlrpc.php { deny all; return 403; } so requests never reach WordPress.— Step 3: If a plugin (Jetpack, app login) needs it, whitelist Automattic IP ranges instead of opening it globally.
— Step 4: Kill pingbacks specifically. Filter
xmlrpc_methods to unset pingback.ping — this stops your site being a DDoS reflector.— Step 5: Verify. Re-request the file, expect 403. Check logs for system.multicall amplification attempts.
Run this every time.
Username Enumeration Block SOP
Run on every WordPress site — usernames are half the credential.
— Step 1: Block author archive scanning. Redirect or 403
— Step 2: Lock the REST users endpoint for unauthenticated requests (see the REST API SOP).
— Step 3: Disable author-slug leakage. Set the
— Step 4: Generic login errors. "Invalid credentials," never "unknown username."
— Step 5: Block enumeration in
— Step 6: Verify with an enumeration tool that no endpoint returns a real login name.
Run this every time.
Run on every WordPress site — usernames are half the credential.
— Step 1: Block author archive scanning. Redirect or 403
/?author=1 through /?author=N.— Step 2: Lock the REST users endpoint for unauthenticated requests (see the REST API SOP).
— Step 3: Disable author-slug leakage. Set the
nicename different from the login so pretty permalinks don't expose it.— Step 4: Generic login errors. "Invalid credentials," never "unknown username."
— Step 5: Block enumeration in
sitemap-users.xml if your SEO plugin generates one.— Step 6: Verify with an enumeration tool that no endpoint returns a real login name.
Run this every time.
Myth-Busting SOP: 'Daily Backups Mean We're Protected'
Backups are recovery, not prevention — and an untested backup is a hope, not a control. They also restore the same backdoor the attacker left. Harden the practice:
— Step 1: Keep backups off the production server and outside the web user's reach (ransomware targets local copies first).
— Step 2: Apply 3-2-1: three copies, two media, one off-site and immutable.
— Step 3: Scan backups for known malware before restoring, or you reinstate the breach.
— Step 4: Test a full restore quarterly — measure actual recovery time.
— Step 5: Verify retention is long enough to predate a slow-burn compromise (30-90 days).
Backups limit damage; they don't stop attacks. Run this every time.
—
Про no code platform debates подробнее — @DragDropDone
Backups are recovery, not prevention — and an untested backup is a hope, not a control. They also restore the same backdoor the attacker left. Harden the practice:
— Step 1: Keep backups off the production server and outside the web user's reach (ransomware targets local copies first).
— Step 2: Apply 3-2-1: three copies, two media, one off-site and immutable.
— Step 3: Scan backups for known malware before restoring, or you reinstate the breach.
— Step 4: Test a full restore quarterly — measure actual recovery time.
— Step 5: Verify retention is long enough to predate a slow-burn compromise (30-90 days).
Backups limit damage; they don't stop attacks. Run this every time.
—
Про no code platform debates подробнее — @DragDropDone
File Permission Audit SOP
Run on any server you inherit or after a suspected breach.
— Step 1: Directories to 755, files to 644. Run
— Step 2: Lock
— Step 3: Verify ownership. Web files owned by the app user, never
— Step 4: Hunt for 777.
— Step 5: Confirm
Run this every time.
Run on any server you inherit or after a suspected breach.
— Step 1: Directories to 755, files to 644. Run
find . -type d -exec chmod 755 {} \; then find . -type f -exec chmod 644 {} \;.— Step 2: Lock
wp-config.php to 640 (or 600 if PHP runs as owner). Never 644 on shared hosting.— Step 3: Verify ownership. Web files owned by the app user, never
root and never the web server user as owner.— Step 4: Hunt for 777.
find . -perm -o+w -type f — anything world-writable is a backdoor waiting to happen.— Step 5: Confirm
uploads/ can't execute PHP. Add a deny rule for .php in that directory.Run this every time.
2FA Enforcement Rollout SOP
Use when mandating two-factor across a team, not just suggesting it.
— Step 1: Pick TOTP or hardware keys. Disable SMS — SIM-swap defeats it.
— Step 2: Set a grace window. 7 days from first login to enroll, enforced by plugin policy.
— Step 3: Force enrollment by role. Require it for Administrator and Editor first; expand to all roles after.
— Step 4: Generate and store recovery codes offline. Test one to confirm it consumes correctly.
— Step 5: Block the grace bypass. After day 7, unenrolled accounts get login-locked, not warned.
— Step 6: Audit monthly. Pull a list of accounts without an active 2FA secret and remediate.
Run this every time.
Use when mandating two-factor across a team, not just suggesting it.
— Step 1: Pick TOTP or hardware keys. Disable SMS — SIM-swap defeats it.
— Step 2: Set a grace window. 7 days from first login to enroll, enforced by plugin policy.
— Step 3: Force enrollment by role. Require it for Administrator and Editor first; expand to all roles after.
— Step 4: Generate and store recovery codes offline. Test one to confirm it consumes correctly.
— Step 5: Block the grace bypass. After day 7, unenrolled accounts get login-locked, not warned.
— Step 6: Audit monthly. Pull a list of accounts without an active 2FA secret and remediate.
Run this every time.
Least-Privilege Role Cleanup SOP
Run quarterly on every WordPress install.
— Step 1: Count your Administrators. More than 2-3 on a normal site is a finding, not a feature.
— Step 2: Demote content people. Writers get Author, reviewers get Editor. Nobody publishes copy from an Admin account.
— Step 3: Remove
— Step 4: Set
— Step 5: Delete dormant accounts. No login in 90 days = disable, then remove after review.
— Step 6: Reassign orphaned content before deletion so nothing breaks.
Run this every time.
Run quarterly on every WordPress install.
— Step 1: Count your Administrators. More than 2-3 on a normal site is a finding, not a feature.
— Step 2: Demote content people. Writers get Author, reviewers get Editor. Nobody publishes copy from an Admin account.
— Step 3: Remove
edit_files, install_plugins, and update_core from any custom role that doesn't deploy.— Step 4: Set
DISALLOW_FILE_EDIT to true in wp-config.php — kill the in-dashboard code editor entirely.— Step 5: Delete dormant accounts. No login in 90 days = disable, then remove after review.
— Step 6: Reassign orphaned content before deletion so nothing breaks.
Run this every time.
