KM Associates of New York
Structural engineering firm in NYC. G&M has worked with them previously. Currently re-engaging for an urgent website security engagement (kmaofny.com was compromised in April 2026).
Project Info
| Job Number | TBD — not yet assigned |
| Phase | Active — Security incident response |
| Billable | TBD — needs confirmation |
Client Contacts
- Liz Warren — Operations Manager, lwarren@kmaofny.com, Direct: (917) 470-9485
- Colleen Quinn — cquinn@kmaofny.com
- KM Associates Main Office — 14 Penn Plaza, 16th Floor, New York NY 10122 / (212) 563-6760
Related parties on the security thread
- Jessica Wyman — jessica@wymanprojects.com — handling removal of unauthorized pages
- RGC @ Neighbortech — rgc@neighbortech.net — KMA's IT/network vendor
- AECOM — external company that flagged KMA's domain for malicious activity (context: KMA's email to AECOM is being blocked pending a full security audit)
Site Reference
| Local Path | /Users/edowns/Local Sites/km-associates/app/public/wp-content/themes/km_associates |
| Local URL | https://km-associates.local |
| Production URL | https://kmaofny.com |
| GitHub | TBD — no local .git found in theme |
| Host / Hosting | TBD — need to confirm where kmaofny.com is hosted (Flywheel? other?) |
| Theme Docs | Custom WordPress theme. No CLAUDE.md at theme root. Has acf-json/, custom CPTs: clients, faqs, projects, careers. |
Integrations
| Service | Status | Details |
|---|---|---|
| Harvest | ✅ Active | Client: KM Associates (id 4335283) · Project: Web Fixes (id 48048591) · Task: Web Updates (id 4840855) · Billable at studio rate |
| Todoist (Team) | TBD | Not set up |
| Todoist (Dev) | TBD | Not set up |
| Basecamp | TBD | Check if existing KMA project exists |
| Masterdoc | TBD | Need to locate or create Masterdoc in Drive |
| Notion | None | Not in use for this project |
| Figma | N/A — no design work scoped | This is security/cleanup work, not a redesign |
| Cloudflare | TBD | Need to confirm if kmaofny.com is on Cloudflare |
| SendGrid | TBD | Likely unrelated to current issue |
Quick Links
| What | Where |
|---|---|
| WP admin — All users | https://kmaofny.com/wp-admin/users.php |
| Wordfence — Login Security (2FA) | https://kmaofny.com/wp-admin/admin.php?page=WFLS |
| Wordfence — All Options / Email Alerts | https://kmaofny.com/wp-admin/admin.php?page=WordfenceSecOpt |
| Wordfence — Scan findings | https://kmaofny.com/wp-admin/admin.php?page=WordfenceScan |
| Wordfence Central dashboard (site view) | https://wordfence.com/central/ — site ID a19d21c1-a18d-43ec-b1d0-267aa3be516f |
| Flywheel site dashboard | https://app.getflywheel.com/ (site: kmaofny) |
| KMA Masterdoc — Client Logins tab | https://docs.google.com/spreadsheets/d/1WUmbvNjOPUPwHziIqI1jOj8XnYyF_0TQEjjEzMjTOm0 |
2FA Status — "Required" vs "Activated" (read this before worrying)
If a user shows "Not activated" in Wordfence Login Security, that does not mean 2FA is optional for them. Two separate concepts:
- Required — controlled by role-level toggles in Wordfence LS settings. On this site: Administrator = required, Editor = required, grace period = 0 (no soft-enforcement). Confirmed in
wp_wfls_settings. - Activated — per-user enrollment state. Shows "Not activated" until a user logs in and pairs a TOTP app. At that first login, Wordfence blocks them at the login screen and forces them to pair before reaching the dashboard.
Since admin + editor are the only roles in use (no authors/contributors/subscribers on this site), all 5 users are covered by the requirement. Current activation state as of 2026-04-23 PM:
| User | Role | Activated? |
|---|---|---|
| gmlaunch (Eric) | Admin | ✅ Paired via 1Password |
| KM_Associates | Admin | Pending first login |
| ewarren (Liz) | Admin | Pending first login |
| cquinn (Chris) | Admin | Pending first login |
| jwyman (Jessica) | Editor | Pending first login |
Wordfence alert email recipients (set 2026-04-23 PM)
wp_wfconfig.alertEmails = cquinn@kmaofny.com,lwarren@kmaofny.com,support@neighbortech.net. Change via wp-admin → Wordfence → All Options → Email Alert Preferences, or via wp eval 'wfConfig::set("alertEmails", "...");' over SSH.
Current Issue — Security Incident (2026-04-22)
What happened: - kmaofny.com was compromised. Unauthorized pages (including foreign-language content) were added by an attacker. - AECOM (external party) flagged the kmaofny.com domain for malicious activity. - KMA is receiving emails from AECOM, but outbound emails from kmaofny.com are not reaching AECOM. - AECOM has asked KMA to complete a full security audit before they will review again. - Liz is concerned the same thing could be happening with other clients.
What's already underway: - Jessica Wyman is removing the visible unauthorized pages.
What KMA is asking G&M for: - A deeper security audit and cleanup: - Identify and remove any remaining malicious code / backdoors - Password resets and credential rotation - Lock the site down going forward - Time-sensitive — Liz asked for availability and timing.
Recon — 2026-04-22 (what I've pulled so far)
Infrastructure:
- Site A record: 151.101.130.159 — that's a Fastly edge IP. Origin is behind Fastly CDN, not directly reachable by IP.
- Nameservers: EarthLink (dns1/2/3.earthlink.net) — legacy ISP DNS hosting
- MX: arsmtp.com — AppRiver / Zix Email Security (inbound filtering/security layer)
- SPF: v=spf1 include:spf.protection.outlook.com -all — outbound via Microsoft 365 / Exchange Online
- DMARC: v=DMARC1; p=none; — DMARC published but in monitor-only mode. Not enforcing alignment. This is almost certainly a major contributor to the AECOM delivery issue.
- DKIM: not yet checked (need to look up M365 default selectors selector1._domainkey / selector2._domainkey)
Business context (from kmaofny.com): - NYC building-code + zoning consulting, project expediting, special inspections - Clients include Amazon, Apple, JPMC, Columbia, Verizon, Cartier, Whole Foods - 60,000+ applications filed, 111M+ sq ft construction completed - Tagline: "Moving Projects Forward" - Sister company: Crosscheck Inspections
Public site surface: - Homepage + nav look clean — no foreign-language spam visible to crawler - Either Jessica Wyman already removed the visible pages OR the unauthorized pages are at URLs not in the nav (orphaned / sitemap-only)
WordPress login page:
- wp-login.php is publicly accessible
- No 2FA visible, no apparent rate-limiting
- This is a likely attack vector — combined with the compromise, login hardening should be priority one after cleanup
1Password sweep: - Searched all vaults (G&M, Personal, Ventures, Claude Bot) — zero matches for KMA / kmaofny / km associates - Either the creds are stored under a different title, in a shared team vault I can't see, or we don't have them - Blocker: need production access before forensic work can start
Local copy status:
- Theme: ~/Local Sites/km-associates/app/public/wp-content/themes/km_associates (custom G&M-built, last touched June 2025)
- Has wp-login-lockdown.php in theme functions — some login hardening was built in at theme level
- Plugins: ACF Pro + ACFE Pro, FacetWP + Map + Flyout, Gravity Forms, Relevanssi, Yoast, Redirection, Imagify, WP All Import Pro, CPT UI, Admin Columns Pro — full G&M stack, confirms prior build
- The Local copy is stale — likely not reflective of current production state
Working theory
Two likely root causes, not mutually exclusive:
- WP admin compromise — weak/reused admin password, no 2FA, no rate-limiting on wp-login.php → attacker got in, added foreign-language SEO spam pages (common "Japanese keyword hack" or similar doorway page attack for pharma/gambling SEO).
- DMARC not enforcing —
p=nonemeans spoofed emails claiming to be from @kmaofny.com are not being rejected by receiver policy. If the attacker is also using the domain to send spam, AECOM's gateway is blocking all mail from the domain because the reputation tanked. Withoutp=quarantineorp=reject, we can't stop that.
The email delivery issue and the page injection are symptoms of the same compromise, not two unrelated problems.
Investigation Checklist (to run today)
- [ ] Confirm production host + get SSH/SFTP access (is this on Flywheel? legacy host?)
- [ ] Confirm WP admin access (who has accounts, when were passwords last rotated?)
- [ ] Clone production DB and files locally for safe forensic work
- [ ] Scan for malicious PHP (common patterns:
eval(base64_decode(,gzinflate(base64_decode(, obfuscated strings in theme/plugin files, files dated after the incident) - [ ] Check
wp_optionsfor rogue entries (unexpectedactive_plugins,siteurl,home) - [ ] Check
wp_usersfor unknown admin accounts - [ ] Check
wp-content/uploadsfor .php files (should never contain executable PHP) - [ ] Check
.htaccessfiles for injected redirects - [ ] Review file modification timestamps across wp-content/
- [ ] List all installed plugins — identify outdated or abandoned ones
- [ ] Check DNS + domain reputation: MXToolbox blacklist check, Google Safe Browsing status
- [ ] Verify email deliverability setup (SPF, DKIM, DMARC) — this is likely why AECOM is blocking them
- [ ] Capture screenshots + a findings doc to present back to Liz
Project Status
2026-04-22 — Phase 1 complete, Phase 2 in progress
Phase 1 — Stop the Bleeding: COMPLETE (~20 min). All attacker access vectors neutralized on production:
- bot-token REST API application password revoked (never used — good)
- bot + admlnlx rogue admins demoted to subscriber
- 6 web shells in /www/ quarantined (renamed .quarantine, verified 404 externally)
- fileview.php stray shell in simple-301-redirects/assets/css/ quarantined
- 3 malicious plugin folders renamed to .quarantine (one_images_user, widget-1776587769, widget-1776587767)
- 5 legit admin passwords reset to strong unique values (stored at forensic-workspace/NEW-PASSWORDS-20260422.txt, mode 600)
- Salt rotation: wp-cli blocked (wp-config.php root-owned on Flywheel). Requires manual click in Flywheel dashboard → Site → Advanced → Shuffle Secret Keys. Eric to handle.
- Full DB (14MB) + wp-content tarball (45MB) snapshotted locally as rollback baseline.
Client communication: - 2:48 PM CDT — Eric emailed Liz a layman's explanation of the compromise + cleanup plan, including a 3-4 hour cleanup estimate at G&M's $225 studio rate, requesting formal approval. - Eric is proceeding with Phase 2 proactively without waiting for written approval (decision made 2026-04-22 afternoon).
Phase 2 — Full Cleanup: IN PROGRESS. Decisions made:
- wp-file-manager v8.0.3 (the entry point, CVE-2020-25213) will be REMOVED entirely, not updated.
- All 9 premium plugins will be reinstalled from fresh vendor downloads (keeping scope equivalent to current site).
- training-videos is a custom G&M plugin (verified by reading plugin header in prod) — no reinstall needed, source is with us.
- 17 free plugins will be reinstalled from wp.org via wp plugin install --force.
1Password access finding:
- Claude Code is authenticated to 1Password via a service account ("Claude Bot" integration).
- That service account only has access to the "Claude Bot" vault, not the G&M vault.
- Eric is granting Claude Bot service account read access to the G&M vault so Claude can pull the premium plugin licenses automatically.
- Best practice recommendation: Store ALL plugin licenses in 1Password G&M vault going forward. Create a naming convention like Plugin License — [Plugin Name] so Claude can fuzzy-match on future engagements.
Billing: - This is billable at $225/hour studio rate. - Estimated 3-4 hours total for Phases 1 + 2. - Phases 3 (email reputation) + 4 (hardening + portfolio audit) will be separately scoped — likely another 2-3 hours.
Status: Phase 2 active — waiting on Eric for FacetWP license activation + 4 premium plugin ZIPs.
Phase 2 progress (as of mid-afternoon)
Completed:
- All 16 free plugins reinstalled fresh from wp.org (wp plugin install --force)
- wp-file-manager REMOVED entirely (the entry point, CVE-2020-25213)
- simple-add-pages-or-posts REMOVED (closed on wp.org for security)
- 2013 google-analytics-for-wordpress empty shell REMOVED
- ACF Pro reinstalled via WP-CLI (6.7.0.2 → 6.8.0.1)
- Gravity Forms reinstalled via WP-CLI (2.9.28 → 2.10.0)
- All rogue DB options deleted
- All quarantined malicious plugin folders deleted from disk
- All quarantined web shells deleted
Remaining:
- FacetWP + Flyout + Map Facet reinstall (blocked: license not activated for kmaofny.com on facetwp.com/account — Eric approving)
- Admin Columns Pro, ACF Extended Pro, WP All Import Pro, WPAI ACF Add-On reinstall (blocked: Eric dropping ZIPs in ~/Desktop/kma-premium-plugins/)
- Second-pass Wordfence scan
- Functional test
- Delete bot + admlnlx user records (holding for final evidence preservation)
Plugin count now: 27 (was 31 before cleanup). Site still returning 200.
Key discoveries / new artifacts
- New skill:
~/.claude/skills/plugins/SKILL.md— per-vendor reference for premium plugin fetching. Covers ACF Pro, ACF Extended Pro, Gravity Forms, FacetWP (+ add-ons), Admin Columns Pro, WP All Import Pro (+ ACF add-on). Documents the three paths: SSH-only, vendor API with license, manual ZIP. Includes a diagnostic command that dumps every plugin's available download URL from the WP update transient. - FacetWP license activation gotcha: If a site URL isn't activated against the FacetWP license, their API returns update info but omits the download URL.
wp plugin updatethen fails with "Update package not available". Fix: log into facetwp.com/account → find site in "Pending" → approve. - Per-vendor API findings documented in the skill. Future reinstall workflows will be faster.
Docs
| File | Description |
|---|---|
| INCIDENT-REPORT.md | Full forensic incident report — client-facing, ready to share with KMA / AECOM / insurance when finalized |
| REMEDIATION-PLAN.md | 4-phase execution plan |
| DNS-CHANGES.md | Running log of every DNS modification made (for client email) |
| forensic-workspace/ | Malicious file evidence, DB snapshot, password files, screenshots |
~/Desktop/kma-aecom-email-for-liz.md |
Forwardable AECOM summary for Liz to send to her AECOM contact |
🅿️ Session parked: 2026-05-06 ~3:40 PM CDT (RESUME FROM HERE)
Today's work (2026-05-06)
Wordfence 2FA enforcement — actual root cause found. The 2026-04-28 entry attributed Jessica/Liz/KM_Associates not being prompted to a 10-day "user grace period" introduced by Wordfence 8.1.4. Clearing that on 2026-04-28 helped Chris (he found the panel link manually), but it didn't fix the underlying problem. Jessica logged in again on 2026-04-29 and 2026-05-05 and was never prompted. The actual root cause: Wordfence 8.x renamed the role-required setting key from require_2fa.<role> (legacy, what we wrote in April) to required-2fa-role.<role> (current). The plugin's does_user_role_require_2fa() reads the new key only. Both rows for that key were missing from wp_wfls_settings, so requires_2fa returned false for every user, meaning Wordfence has been silently no-op enforcing 2FA on this site since the 8.1.4 auto-update around 2026-04-25. Confirmed via the plugin's own getter: Controller_Settings::shared()->get_required_2fa_role_activation_time("administrator") returned false.
Fix on prod. Set both required-2fa-role.administrator and required-2fa-role.editor to time() via Controller_Settings::shared()->set(). Verified via reflection on does_user_role_require_2fa() that Jessica now returns requires_2fa=true, in_grace=NULL, required_at=1778097526. Then force-destroyed all WP sessions for jwyman, ewarren, KM_Associates so each will be prompted on their next login. Chris and Eric (already enrolled) are unaffected.
Jessica promoted to administrator. Per Liz's request earlier on the 2FA thread, jwyman (user 11) was set-role from editor → administrator. She'll see Gravity Forms in her sidebar after re-login. The role change combined with the new role-required key now applies, so she'll be prompted to enroll 2FA before reaching the dashboard.
Gravity Forms deliverability issue identified (NOT fixed tonight). Jessica reported on 2026-05-05 that her form-notification email landed in Spam and Liz didn't receive the entry at all. Verified Form #6 ("Request Proposal Short") IS correctly configured: lwarren@kmaofny.com, jessica@wymanprojects.com, info@kmaofny.com, psantantonio@kmaofny.com are all on the recipient list. The form config is fine. Real cause: site mail goes out via Flywheel's local PHP mail() path, not through M365 SMTP. After our 2026-04-23 SPF/DMARC tightening (SPF only authorizes spf.protection.outlook.com, DMARC at p=quarantine), anything the site sends now fails authentication and gets filtered. This is a side effect we caused. Two paths to fix, neither shipped tonight:
- Install WP Mail SMTP, point at M365 SMTP (needs an app password from Roberto/Chris on the KMA M365 tenant)
- Install WP Mail SMTP, point at SendGrid (separate signup, isolates KMA's M365 from the WordPress site)
Either is ~1-2 hours plus credential handoff. Should be scoped as a separate small engagement with Liz.
Email reply drafted to Jessica. Gmail draft r-529193132455750159 on thread 19dbb3e06b64a760. To: Jessica. CC: Liz + Chris. Two short paragraphs: confirms admin promotion (Gravity Forms in sidebar) and explains the 2FA root cause + that her next login will force enrollment. Per Eric's call mid-session, the spam-mail explanation was dropped from the draft (line was hand-wavy without an actual ticket); deliverability fix should go to Liz as a separate scoping note, not tucked into a reply to Jessica. Awaiting Eric's review/send.
Internal tooling work (NOT KMA-billable)
In the same session, Eric asked me to bulletproof Gmail drafting against three recurring failure modes: tone-profile leaks (em-dashes, AI tells, jargon), Reply-All disrespect (fresh chains instead of inline replies, dropped CCs), and hard line breaks within paragraphs. Plan and implementation captured at ~/.claude/plans/i-use-email-constantly-wild-jellyfish.md. Phases shipped tonight:
- Phase 1: extended existing
gmail-em-dash-guard.pyandgmail-html-required-guard.pyPreToolUse hooks to covermcp__claude_ai_Gmail__create_draft(the official Anthropic Gmail integration) — previously only the legacymcp__gmail__*MCP was guarded. - Phase 2: added Rule 5 to
gmail-html-required-guard.pyblocking literal\ninside<p>...</p>blocks. - Phase 3 Check A (block-mode): new
gmail-reply-context-guard.pydenies any "Re:" subject without a parent-message reference. - Phase 3 Check B (warn-only Phase A): new
gmail-thread-breadcrumb.pyPostToolUse hook captures thread participants to/tmp/claude-gmail-breadcrumbs/. Reply-context guard reads that breadcrumb whenreplyToMessageIdis set and warns to stderr +~/.claude/logs/gmail-reply-warnings.logif any original recipient is missing. One-line promotion to block-mode after a week of clean observation.
34-case test harness at ~/.claude/hooks/test-fixtures/gmail-guards.test.sh, all passing. Latency 30 ms per hook. Wired in both ~/.claude/settings.json and ~/.claude-royal/settings.json. Hook scripts are hardlinked between ~/.claude/hooks/ and ~/.claude-royal/hooks/.
Current enrollment state on prod (replaces the 2026-04-28 table)
| User | Role | 2FA |
|---|---|---|
| gmlaunch (Eric) | Admin | ✅ enrolled |
| cquinn (Chris) | Admin | ✅ enrolled |
| ewarren (Liz) | Admin | ❌ not yet — sessions cleared, will prompt on next login |
| KM_Associates | Admin | ❌ not yet — sessions cleared, will prompt on next login |
| jwyman (Jessica) | Admin (promoted today) | ❌ not yet — sessions cleared, will prompt on next login |
Gotcha for future sessions (replaces the 2026-04-28 gotcha)
When auditing Wordfence Login Security on a 8.x install, don't trust the legacy require_2fa.<role> keys. They are no longer read. Check required-2fa-role.<role> instead. Quick validation:
ssh <site>+<theme>@ssh.getflywheel.com "cd /www && wp eval 'use WordfenceLS\\Controller_Settings; echo Controller_Settings::shared()->get_required_2fa_role_activation_time(\"administrator\") === false ? \"NOT-SET\" : \"SET\"; echo PHP_EOL;'"
If this returns NOT-SET, 2FA enforcement is silently broken regardless of what the legacy keys or the WFLS UI shows. Set via Controller_Settings::shared()->set("required-2fa-role.<role>", time());. Also worth auditing every other G&M client site running Wordfence 8.x — same migration likely caused the same silent break elsewhere.
Outstanding (not blocking)
- Liz / KM_Associates / Jessica still need to enroll 2FA. Forced on next login now that the schema fix is in place. Watch the inbound thread (
19dbb3e06b64a760) for questions. - Gravity Forms / outbound mail authentication — needs separate scoping conversation with Liz. Two paths (M365 SMTP relay vs SendGrid). ~1-2 hours plus credential handoff. NOT yet pitched.
- Maintenance pitch (from 2026-04-28) still awaiting Eric's send approval. KMA Harvest project list shows only "Web Fixes" active — no maintenance project exists yet. Today's 0.5h was logged to G&M / Customer Service with a Todoist note for Brooke to transfer to KMA Maintenance once signed.
- Local project-groups.json bug fixed. Was wired with
client_id: 4335283(which is actually Coneflower Creamery in Harvest); corrected to12323529(the real KMA client). Unsure if the time-entries we logged on 2026-04-24 went to the right project —project_id: 48048591IS the correct KMA "Web Fixes" project (verified via Harvest API), so the time landed correctly even though the client_id in the local cache was wrong. The client_id was only a local lookup convenience; project_id is the actual destination. No correction needed on past entries. - All prior 2026-04-28 outstanding items still pending: DMARC graduation observation window, audit other G&M sites for the same Wordfence schema gap, audit other G&M sites for the weak
.CLIENT<service>158!Earthlink/Flywheel password pattern, KMA → Maintenance Portal monthly monitoring.
Reference for resuming
- All prod state changes from tonight verified live; no rollback needed.
- Plan file for the hook system:
~/.claude/plans/i-use-email-constantly-wild-jellyfish.md - New / updated hooks:
gmail-em-dash-guard.py,gmail-html-required-guard.py,gmail-reply-context-guard.py,gmail-thread-breadcrumb.py - Test harness:
bash ~/.claude/hooks/test-fixtures/gmail-guards.test.sh - Phase 3 Check B promotion: edit
CHECK_B_PHASE = "A"→"B"at the top ofgmail-reply-context-guard.pyonce a week of clean warns has accumulated in~/.claude/logs/gmail-reply-warnings.log.
🅿️ Session parked: 2026-04-28 ~6:15 PM CDT
Today's work (2026-04-28)
Wordfence weekly summary follow-up (Liz). Liz forwarded the standing weekly summary asking whether the highlighted real-looking usernames in the Top 10 Failed Logins list were a problem. Diagnosed: the highlighted names that aren't real KMA accounts (lsanchez, vgerbino, chartel, etc.) are bot wordlists; the only real account they hit was km_associates and Wordfence shut them down via lockout. Drafted a plain-English explanation and sent.
Maintenance pitch drafted for Liz (CC Brooke + Kristin). Pattern E proactive pitch. Now that the cleanup is wrapped, packaging maintenance plan as the natural follow-on. Pitch leads with what was preventable about the original incident, names what's included, and brings the G&M Maintenance Portal in as a client-facing differentiator framed in KMA's terms. Gmail draft r811697463585903205. Awaiting review/send.
Production 2FA enforcement fix. Discovered Wordfence had auto-updated to 8.1.4 since our 2026-04-23 hardening, and the new schema introduced a 2fa-user-grace-period setting defaulting to 10 days. That was overriding our role-required setting and letting unenrolled users (Chris, Jessica) log in without an enrollment prompt. Cleared the value to 0 on production. Forensic record: forensic-workspace/wfls-settings-CHANGE-2026-04-28-PM.md.
Replies on the 2FA setup thread. Chris ack (he enrolled the long way, manually via Wordfence panel). Jessica got a longer reply explaining why the prompt didn't fire (the 10-day grace bug, now fixed) plus an answer on Gravity Forms not being in her Editor sidebar (default WP behavior; Liz still gets all form submissions because notifications route to info@kmaofny.com). Both drafts on thread 19dbb3e06b64a760.
Skills work (outside this repo, listed for cross-reference). Created ~/.claude/skills/maintenance/VALUE-AND-SALES.md (sales companion to the operational SKILL.md), cross-linked maintenance/SKILL.md and gm-portal/SKILL.md, created ~/.claude/project-notes/gm-maintenance/README.md index, saved feedback memory feedback_sales_voice_client_perspective.md (rule: sales emails articulate value in CLIENT terms, not vendor terms).
Current enrollment state on prod
| User | Role | 2FA |
|---|---|---|
| gmlaunch (Eric) | Admin | ✅ enrolled |
| cquinn (Chris) | Admin | ✅ enrolled |
| ewarren (Liz) | Admin | ❌ not yet |
| KM_Associates | Admin | ❌ not yet |
| jwyman (Jessica) | Editor | ❌ not yet |
Next login by any of the bottom three should now force enrollment thanks to the grace-period fix.
Gotcha for future sessions
When Wordfence updates, audit wp_wfls_settings for new keys with permissive defaults. The 8.x line is migrating from underscored keys (require_2fa.administrator) to dashed keys (require-2fa.administrator); right now both schemas coexist with the dashed ones empty. If a future update flips to reading the dashed schema preferentially, the empty values would silently disable enforcement.
Outstanding (not blocking)
- Liz / KM_Associates / Jessica still need to enroll 2FA. Should happen automatically next time they log in (post-fix). Watch for inbound questions.
- Maintenance pitch awaiting Eric's review before send. Brooke and Kristin are CC'd to handle agreement follow-up if Liz says yes.
- All prior open items from 2026-04-27 (DMARC graduation observation window, audit other G&M sites for the same patterns, KMA → Maintenance Portal monthly monitoring) remain pending.
🅿️ Session parked — 2026-04-27 ~12:30 PM CDT
2026-04-27 (cont.) — Wordfence alert tuning
Roberto forwarded a routine "user locked out" alert from 2026-04-25 (a brute-force attempt from an Ethiopian IP that Wordfence blocked correctly — the system worked, nothing for a human to do). That kind of event is constant background noise on any public WordPress site. Tuned the alert flags so the team only gets emailed for events that actually require human action.
Diff applied via SSH + wfConfig::set():
| Wordfence key | Before | After | Reason |
|---|---|---|---|
alertOn_loginLockout |
1 | 0 | Brute-force lockouts are constant noise; the lockout itself is the defensive action. |
alertOn_block |
1 | 0 | Firewall blocks automatically; no human action needed. |
alertOn_breachLogin |
1 | 0 | Strong unique passwords + 2FA already cover this at credential level. |
alertOn_lostPasswdForm |
1 | 0 | Surprise default. Password-recovery attempts happen all the time. |
alertOn_firstAdminLoginOnly |
0 | 1 | Now only fires admin-login alerts the first time a user signs in from a new device. |
alertOn_severityLevel |
25 (Low+) | 100 (Critical only) | Scan-finding alerts now fire only for Critical severity (malware-tier), not Low/Medium warnings. |
Unchanged (still ON): alertOn_scanIssues, alertOn_adminLogin (paired with firstAdminLoginOnly), alertOn_wordfenceDeactivated, alertOn_wafDeactivated. Already OFF before tuning: alertOn_throttle, alertOn_update, alertOn_nonAdminLogin, alertOn_firstNonAdminLoginOnly. Rate cap alert_maxHourly = 5 left in place.
Recipients unchanged: cquinn@kmaofny.com, lwarren@kmaofny.com, support@neighbortech.net.
What recipients will still get emails for (the things that actually need a human): - Wordfence scan finds malware or critical infection (severity = Critical) - Wordfence plugin gets deactivated - WAF (firewall) gets deactivated - An admin signs in from a brand-new device for the first time
What they will no longer get emails for: - Routine brute-force lockouts (the system handled it) - IP blocks from the firewall - Password-recovery attempts - Logins from already-known devices - Low/Medium scan findings
Forensic record: before/after dumps at forensic-workspace/wordfence-alert-config-before-2026-04-27.txt and wordfence-alert-config-after-2026-04-27.txt.
Skipped: the controlled-lockout live test from the plan. DB-level verification is dispositive (Wordfence reads the flag at email-send time; no cache layer), and the live test would have locked out a clean test IP for 4 hours unnecessarily.
2026-04-27 (cont. 2) — Salesforce DKIM piece officially closed ✅
Final verification arrived ~12:11 PM CDT. Chris forwarded the full Outlook headers from a Salesforce-to-criewaldt@kmaofny.com test (the original failure path: Salesforce → AppRiver → Microsoft 365). Authentication-Results came back all green:
dkim=pass (signature was verified) header.d=kmaofny.com
dmarc=pass action=none header.from=kmaofny.com
compauth=pass reason=100
The compauth=pass reason=100 is the bit. On Friday's flagged message it was compauth=fail reason=605 — that's exactly the code Microsoft uses to render the "can't verify sender" banner. Now it's pass. Banner is gone for good.
AppRiver's own headers (separate from Microsoft's) also confirm independently: X-Note: DKIM: Pass: kmaofny.com, X-Note-DMARC: DMARC: Pass, X-Note-Adkim: DMARC/ADKIM aligned: Header sender domain matches DKIM header domain. Two verifiers agree.
The DKIM-Signature header in the test message used selector s=kma2026, confirming the new key we activated this morning is what's actively signing live mail.
Eric sent a wrap-up note to the client confirming the Salesforce email-authentication piece is officially complete.
Engagement piece status: ✅ Salesforce DKIM email authentication — closed.
Outstanding KMA items remaining: legacy DKIM selector cleanup pending Chris's reply on the older km / kmdnsselector keys (low priority — they've been silently broken for years, no rush), and Liz/Laura re-send of Friday's quarantined invoice batch (Liz's call, no action on our side).
2026-04-27 (cont. 3) — Crosscheck DKIM scoping question (parked, awaiting client decision)
Chris replied on the same DKIM thread (19dcf14c95c230d9, ~1:43 PM CDT) asking whether we manage DNS for crosscheckinspections.com as well — the sister-company domain — because they need a DKIM key on that side too.
Recon (15 min):
- Crosscheck nameservers:
dns1/2/3.iwantmyname.com— NOT Earthlink/Carrierzone. Different DNS provider, different account entirely. We have no access on file. - Confirmed against KMA Masterdoc Client Logins tab: only Earthlink (kmaofny.com zone) is listed; no iwantmyname creds.
- Crosscheck's existing email-auth posture is otherwise good:
SPF: v=spf1 include:spf.protection.outlook.com include:aspmx.pardot.com include:_spf.salesforce.com -all✅DMARC: v=DMARC1; p=none;✅ (monitor mode — same starting point KMA had)- No existing Salesforce DKIM selectors found at common candidate names (
kma2026,cci2026,crosscheck2026,sf,selector1,selector2,km,kmdnsselector) - Net: the only outstanding piece on Crosscheck is the DKIM CNAME publish. SPF/DMARC are already correctly configured.
Reply drafted to Chris (CC Liz, Roberto) on thread 19dcf14c95c230d9 — Gmail draft r-6513533522790793220. Asks two things:
- Add
eric@grainandmortar.comas a user on the iwantmyname.com account that holds crosscheckinspections.com so we can publish records. - Approve another 1-2 hours of scope at $225 studio rate to cover the work.
Once both are in place, the workflow mirrors what we just ran for KMA: Chris generates the key in the Crosscheck Salesforce org → sends the CNAMEs → I publish at iwantmyname → verify propagation → he clicks Activate.
Status: Parked. Eric to review the draft in Gmail and send. Then waiting on Chris/Liz for the access + scope decision.
If they say yes, the next session's playbook is identical to (cont. 1) above except DNS host is iwantmyname.com instead of Earthlink. iwantmyname's UI is more modern (no FQDN-double-append gotcha like Earthlink).
If they say no / not now, close the loop with a "no problem, here's a doc Chris can hand to whoever does manage Crosscheck DNS" reply and move on.
Layman's summary of today's work
The Salesforce email-verification fix is essentially done on our side. Chris Riewaldt (KMA's Salesforce admin, who's back from leave) generated the DKIM key in Salesforce this morning and sent us the two CNAME records we needed to publish. I added both at Earthlink, they propagated worldwide within ~15 minutes (way faster than Earthlink's stated "up to one business day"), and verified from public resolvers. We're now waiting on Chris to click Activate in Salesforce, then Jack to send a test email so I can confirm the Outlook "can't verify sender" banner is gone for good.
While I was in the DNS panel I also noticed the two older Salesforce DKIM records that were set up years ago have been silently broken since day one — whoever entered them typed the full domain into Earthlink's Name field, so Earthlink double-appended .kmaofny.com and the records have been parked at a name no mail server will ever query. They've effectively been zone clutter. Folded a question to Chris into the email asking whether those old selectors are deprecated on the Salesforce side; cleanup is on hold pending his answer.
Two emails sent today: the activation green-light to Chris (CC Liz), and a separate reply to Liz confirming Friday's invoice quarantine was the unsigned-DKIM issue and that Laura should re-send the batch after Chris activates.
Today's work (2026-04-27)
- Chris Riewaldt sent the DKIM record screenshot (thread
19dcf14c95c230d9, subject "DKIM"). Two selectors instead of one because Salesforce required an alternate (kma2026+kmaofny2026). - Both CNAMEs published at Earthlink (~9:30 AM CDT):
kma2026._domainkey.kmaofny.com→kma2026.8rq594.custdkim.salesforce.com.kmaofny2026._domainkey.kmaofny.com→kmaofny2026.g39iib.custdkim.salesforce.com.- Propagation verified from Google (8.8.8.8), Cloudflare (1.1.1.1), and
dns1.earthlink.netwithin ~15 minutes — all four queries return the expected targets. Logged in DNS-CHANGES.md. - Pre-scheduled overnight verification routine disabled (
trig_018wpy1VBBPUxyu4U6B552Kj). Originally set to re-check at 8 AM tomorrow and Slack-DM the result; redundant once propagation completed within the hour. - Found broken pre-existing Salesforce DKIM records during the verify pass:
km._domainkey.kmaofny.comandkmdnsselector._domainkey.kmaofny.comare stored as*.kmaofny.com.kmaofny.com.(FQDN-typed-into-Name-field, Earthlink double-appends). Have never resolved at the queried name. Documented in DNS-CHANGES.md. No deletion yet — pending Chris's confirmation that those selectors are deprecated on Salesforce's end. - 2 emails sent (both via Eric, after my draft):
- To Chris (CC Liz) on thread
19dcf14c95c230d9— confirms records are live, tells him he's clear to Activate, asks Jack to send a test post-activation, AND asks him to check the status of the olderkm/kmdnsselectorselectors so we know whether to clean up the broken DNS or republish. - To Liz (CC Roberto) on thread
19dcf1e0c74102c8— confirms Friday's AppRiver-quarantined invoices were the unsigned-DKIM issue, recommends Laura re-send the Friday batch after Chris activates. - Liz's separate concern (Friday invoice quarantine) was diagnosed and answered. Root cause: Friday's Salesforce invoices went out before DKIM was set up, so AppRiver flagged them as unauthenticated. Future Salesforce mail will be signed and won't trip the same filter.
Waiting on (in priority order)
- ~~Chris Riewaldt — click Activate on both DKIM keys in Salesforce.~~ ✅ Done. DKIM public keys live at the CNAME targets, and the test email's
DKIM-Signatureheader confirms selectorkma2026is signing live mail. - Chris Riewaldt — confirm status of older selectors (
km,kmdnsselector) in Salesforce DKIM Keys panel. (Note: Chris already said in his 2026-04-27 16:23 reply that he doesn't see any previous DKIM keys — meaning the old selectors are inactive on the Salesforce side. We could proceed with the broken-DNS cleanup. Holding for explicit Eric green-light before deleting.) - If deprecated → delete the two broken Earthlink CNAMEs (km._domainkey.kmaofny.com.kmaofny.com.andkmdnsselector._domainkey.kmaofny.com.kmaofny.com.) and log in DNS-CHANGES.md. - If still active → re-publish at the correct (relative) path, which would let those signatures actually verify for the first time. - ~~Jack Ryan — send test email from Salesforce post-activation.~~ ✅ Resolved by Chris's own Salesforce →
criewaldt@kmaofny.comtest (same AppRiver → M365 path Jack would have used). Headers showdkim=pass,dmarc=pass,compauth=pass reason=100. Outlook banner confirmed gone. - Laura Kauffmann (KMA Finance) — re-send Friday's invoice batch after Chris confirms activation. Recommended in today's email to Liz. Liz's call to coordinate; no action on our side.
What I do once Chris replies
| Chris says | I do |
|---|---|
| "Both old selectors are deprecated/inactive in Salesforce" | Delete km._domainkey.kmaofny.com.kmaofny.com. and kmdnsselector._domainkey.kmaofny.com.kmaofny.com. in Earthlink DNS Manager. Log in DNS-CHANGES.md. |
"km (or kmdnsselector) is still active in Salesforce" |
Add fresh CNAME at the correct relative name (km._domainkey or kmdnsselector._domainkey) pointing to the same Salesforce target. Optionally also delete the broken doubled-name version, or leave it as harmless dead weight. |
| "Activate clicked, records show as Active in Salesforce" | Wait for Jack's test email forward. |
What I do once Jack's test email arrives
- Read the
Authentication-Resultsheader in the forwarded message - Confirm
dkim=pass header.d=kmaofny.com,spf=pass,dmarc=pass - Confirm no
compauth=fail reason=605in the headers - Reply on the relevant thread with the confirmation. The Salesforce piece is done.
Open / parked items NOT touched today
- DMARC graduation
p=quarantine→p=reject(still in 1-2 week observation window, started 2026-04-22) - Add DMARC
rua=aggregate reporting (deferred — wantspostmaster@kmaofny.commailbox active first) - KMA in G&M Maintenance Portal for monthly plugin version monitoring (deferred)
- Audit other G&M sites for the weak
.CLIENT<service>158!Earthlink/Flywheel password pattern - Audit other G&M sites for
wp-file-managerinstalls (same CVE that started this whole engagement)
Reference for resuming
- Today's DNS work: DNS-CHANGES.md bottom section
- Active threads:
19dcf14c95c230d9— DKIM activation thread (Chris + Liz)19dcf1e0c74102c8— Invoice quarantine thread (Liz + Roberto)19db676dd49ad9d8— Original "Compromised kmaofny.com Website" thread (full CC group)- Disabled routine for restoration if needed:
trig_018wpy1VBBPUxyu4U6B552Kj(https://claude.ai/code/routines/trig_018wpy1VBBPUxyu4U6B552Kj — Hey Royal workspace) - Earthlink DNS portal:
https://control.earthlink.net/portal/— creds in Masterdoc Client Logins row 11 - Verify any DKIM record fast:
dig +short CNAME <selector>._domainkey.kmaofny.com @8.8.8.8
🅿️ Session parked — 2026-04-24 ~4:00 PM CDT
Layman's summary of today's work (1 hour billable)
Today was project management plus email back-and-forth — no site code changes, no DNS changes. Here's the plain-English version:
What the client reported. Their IT vendor (Roberto at NeighborTech) noticed that an email Jack Ryan sent out through Salesforce was arriving in Outlook inboxes with a yellow "We couldn't verify that this message came from kmaofny.com" banner. Roberto flagged it to me and asked whether our DNS work from yesterday caused it.
What I figured out. Yes — partly. When we tightened their email security yesterday (moved DMARC from "just watching" to "quarantine suspicious mail"), receiving servers like Outlook started actually enforcing the rules. Jack's Salesforce-sent mail fails those rules because Salesforce is not yet set up as an approved sender for kmaofny.com. It's not a bug with our fix; it's a gap Salesforce needs to close on their side. I confirmed the diagnosis by reading the headers of a real flagged email Jack forwarded.
What I sent back. Two emails to Roberto walking him through the fix: a plain-language explanation of the cause, then a second email with click-by-click instructions for whoever runs the Crosscheck Salesforce org to generate a DKIM key and hand me the DNS records to publish. That's the permanent fix.
Where it landed. Roberto replied "we should definitely do the proper fix." Liz confirmed Jack will forward the instructions. Roberto then asked who on the KMA/Crosscheck side actually manages Salesforce — Liz looped in Chris Quinn to run that down (Chris Reiwaldt is out; checking Darryl or Jenna). Ball is in their court to generate the records.
Scope conversation. Flagged to Liz that the original four-hour cleanup estimate is used up and asked for 1-2 more hours to see the Salesforce email piece through. Liz approved in writing. She also asked whether it's risky to wait until Monday if nobody on the Salesforce side is reachable today — I told her no, most of their business email isn't moving over the weekend, so we're fine to pick it up Monday when the right person is back.
Net. One hour of project management and client communication. No production changes. We're parked waiting on the Crosscheck Salesforce admin to generate the DKIM records, at which point I publish them at Earthlink and verify.
Today's work (2026-04-24)
- Roberto (NeighborTech) flagged an Outlook "can't verify sender" warning on an email from Jack Ryan (jryan@kmaofny.com) that was sent out through Salesforce. Diagnosis: the warning is a direct side effect of yesterday's DMARC move from
p=none→p=quarantine. Jack's mail is sent via Salesforce (body referencescrosscheck.my.salesforce.com) but kmaofny.com's SPF does NOT authorize Salesforce, and no Salesforce DKIM selectors exist on the domain. Mail now fails DMARC alignment → Outlook flags. - Contrast: crosscheckinspections.com IS correctly set up — SPF includes
_spf.salesforce.com+aspmx.pardot.com, DMARC is atp=none. That's why Jack's Salesforce mail from the Crosscheck side (if he uses a Crosscheck address) wouldn't trip this. - Correction to prior recon: the 2026-04-22 note claiming Salesforce mail was signed via
kmdnsselector._domainkeyandkm._domainkeyon kmaofny.com was wrong. Those selectors don't resolve. - First email sent to Roberto on thread
19dc01580e396e51(subject "FW: Can't verify sender") explaining the cause, the two remediation paths, and asking Jack to forward the flagged email as an.eml. Cc: Liz. .emlreceived from the client side. File:Peer Me_ P-015727-74-45 Yellowstone Boulevard-Apt 4G-Interior Renovation.eml(in this folder). Headers confirmed:spf=softfailevaluated against*.bnc.salesforce.com(Salesforce VERP bounce domain, not kmaofny.com) — SPF is a red herring here; kmaofny.com isn't the domain being checked at the hop that mattersdkim=none (message not signed) header.d=none— THIS is the actual problem. Salesforce is sending the mail with no DKIM signature at alldmarc=temperrorwithheader.from=kmaofny.com— transient DNS hiccup on Microsoft's end, but even a clean resolve would have failed because DKIM and SPF both can't aligncompauth=fail reason=605— Microsoft's "sender does not pass implicit email authentication" code. That's literally what triggers the "can't verify" banner.- Route: Salesforce MTA → AppRiver (KMA's inbound filter) → Outlook. Mail was delivered, only the banner was the issue.
- Salesforce org identifier:
X-SFDC-User: 005Un000008uTXQ,X-SFDC-LK: 00Do0000000bDFH— that's the Crosscheck org. Someone there is the Salesforce admin we need. - Second email sent to Roberto on the same thread (draft
r4850295149448935139, since sent) with click-by-click Salesforce DKIM setup instructions — Quick Find → DKIM Keys → Create New Key → selectorkma2026/ 2048-bit / domainkmaofny.com, copy the CNAME records back to me BEFORE activating. Corrected read: fix is 95% DKIM-only, no SPF change required. - Roberto replied: "We should definitely do the proper fix here. Liz can you forward the instructions below to Jack?" — approval in hand for the DKIM route.
- Liz replied: "Yes, Jack will forward shortly. Thank you." — Jack is being looped in to run the Salesforce side.
- Scope/billing ask sent to Liz on the original "Compromised kmaofny.com Website" thread — flagged that the original 4-hour estimate is consumed and requested 1-2 more hours at $225 to see the DKIM work through. Liz approved in writing (msg
19dc117e65975d11at 3:04 PM CDT): "Yes, the additional time is approved." She also asked whether waiting until Monday is risky if nobody on the Salesforce side is reachable today. - Weekend-risk reassurance sent to Liz — told her no, most business email isn't moving over the weekend and this is Salesforce-specific, so Monday pickup is fine. Liz: "Got it — thank you."
- Roberto follow-up Q: "Liz would this be Chris Quinn or Chris Reiwaldt?" — asking who actually runs the Crosscheck Salesforce org. Liz replied looping in Chris Quinn, noting Riewaldt is out today and asking about Darryl or Jenna as backups. That's the thread we wait on for the Salesforce admin handoff.
Waiting on
- Crosscheck Salesforce admin (Chris Quinn to route — Riewaldt out today, Darryl/Jenna as possible alternates) to run the 6-step DKIM setup in Salesforce and send us the DNS records Salesforce generates. That's the input we need to publish the DKIM CNAMEs at Earthlink. Confirmed with Liz it's fine if this slips to Monday.
- ~~Liz's approval on the additional 1-2 hours of scope.~~ ✅ Approved 2026-04-24 3:04 PM CDT. Ready to execute DNS work the moment the Salesforce records arrive.
Game plan: "Let's move forward" on Salesforce email authentication
When they approve the proper fix, this is the sequence. Do NOT start any of it until we have the .eml in hand — the Authentication-Results header will confirm whether SPF, DKIM, or both are failing, and which Salesforce domain is actually sending the mail.
Prereqs to confirm first
- Read the
.eml'sAuthentication-Resultsheader. Look for lines likespf=fail smtp.mailfrom=...,dkim=fail header.d=...,dmarc=fail. This tells us exactly what to fix and what the actual sender envelope is. - Confirm with KMA/Crosscheck IT: are they using just Salesforce Sales/Service Cloud, or also Pardot (Account Engagement)? Crosscheck's SPF includes both; KMA's might only need Salesforce core. If unsure, mirror Crosscheck exactly (safer).
- Confirm who has Salesforce admin access in the Crosscheck org. DKIM keys are generated Salesforce-side and they publish the DNS record we have to add. Likely Roberto at NeighborTech or someone internal at Crosscheck. Without this, the fix can't complete.
DNS changes (Earthlink portal, control.earthlink.net)
-
Update SPF on kmaofny.com: - Current:
v=spf1 include:spf.protection.outlook.com -all- Target:v=spf1 include:spf.protection.outlook.com include:_spf.salesforce.com -all- Addinclude:aspmx.pardot.comonly if Pardot is confirmed in use. - Watch the 10-DNS-lookup SPF limit. Current has 1 include; target has 2 or 3. Plenty of room. -
DKIM for Salesforce on kmaofny.com: - In Salesforce Setup: Email Admin → DKIM Keys → Create New Key - Domain:
kmaofny.com, selector: something likesf2026(Salesforce generates the key and provides the exact CNAME/TXT records to publish) - Publish whatever Salesforce specifies at Earthlink (usually two CNAME records pointing to*._domainkey.sendgrid.netor Salesforce-owned DKIM hosts, depending on the Salesforce edition) - Wait for propagation, then activate the key inside Salesforce (Salesforce has an "Activate" button that won't work until DNS resolves cleanly) -
Optional but recommended while we're in the DNS panel: - Add DMARC
rua=reporting:v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@kmaofny.com;— requires that mailbox to accept mail. Gives us aggregate reports from receivers (Google, Microsoft, Yahoo) on who's trying to send as @kmaofny.com.
Verification
dig +short TXT kmaofny.com @8.8.8.8 | grep spf— confirm new SPF propagateddig +short TXT <selector>._domainkey.kmaofny.com @8.8.8.8— confirm DKIM record published- Activate DKIM key in Salesforce
- Have Jack send a test email from Salesforce to an external Gmail or Outlook address you control
- View message headers in the received mail. Confirm
Authentication-Results: spf=pass dkim=pass dmarc=passforkmaofny.com - Confirm the Outlook "can't verify sender" banner does NOT appear on the test
What NOT to do
- Don't roll DMARC back to
p=noneunless KMA explicitly asks for the temporary fix. We just did the hard work to get top=quarantineand AECOM is watching. Giving that back costs us the reputation recovery. - Don't touch
crosscheckinspections.comDNS. That domain is working correctly and isn't ours to manage. - Don't edit SPF by hand from memory. Pull the current record with
dig, copy it, add the new include, replace. Typos here break ALL outbound mail.
Time estimate: ~30 min of DNS work on our end, plus whatever it takes the Salesforce admin to generate keys and activate. Total elapsed ~4-24 hours depending on propagation and admin availability. Billable at $225.
🅿️ Session parked — 2026-04-23 ~11:45 AM CDT
What's DONE on prod
- All Phase 1 actions (stop the bleeding) ✅
- All Phase 2 actions (full cleanup, reinstalls, rogue theme deletion) ✅
- Hijacked API token on
KM_Associatesrevoked ✅ - 3 attacker-published spam posts hard-deleted (Dutch/German casino spam — the original foreign-language pages Liz reported) ✅
- User cleanup: went from 8 users to 3 admins (KM_Associates, gmlaunch, ewarren). Others deleted with content reassigned to KM_Associates (now 485 posts). ✅
- All 3 remaining admin passwords rotated a second time (after hijacked-token discovery). Stored in
forensic-workspace/NEW-PASSWORDS-v2-20260422.txt(mode 600) ✅ - 2026-04-23 v3 batch ✅ Added Chris Quinn (
cquinn, Administrator, cquinn@kmaofny.com) and Jessica Wyman (jwyman, Editor, jessica@wymanprojects.com) as new users per Liz's request. Rotatedewarrenpassword a third time so all three are on the same fresh-credentials timeline. Also correctedewarren's WP email fromewarren@kmaofny.com→lwarren@kmaofny.com. All 3 creds stored inforensic-workspace/NEW-PASSWORDS-v3-20260422.txt(mode 600). Final user state: 4 admins (KM_Associates, gmlaunch, ewarren, cquinn) + 1 editor (jwyman). - 1Password G&M vault — ALL 5 WP users now stored ✅
KMA WP Admin — gmlaunch (G&M)— updated 2026-04-23 (was stale Feb entry with old password)KMA WP Admin — KM_Associates (shared info@)— created 2026-04-23 (didn't exist before)KMA WP Admin — ewarren (Liz Warren)— created this morning, shared 7-day linkKMA WP Admin — cquinn (Chris Quinn)— created this morning, shared 7-day linkKMA WP Editor — jwyman (Jessica Wyman)— created this morning, shared 7-day link- KMA Masterdoc "Client Logins" tab fully reorganized with 4 labeled sections (WP Admin Accounts / Hosting / Plugin Services / Legacy), proper formatting per
/google-sheetsskill ✅ - DMARC changed from
p=none→p=quarantinevia Earthlink DNS portal (propagating — external resolvers still show old value at time of park) ✅ - Functional test passed: homepage, projects, contact (Gravity Forms work), WP admin login with new gmlaunch password ✅
- Wordfence plugin installed + firewall in learning-mode ✅
- Wordfence Free license installed + Central-connected ✅ (2026-04-23 late morning) — API key
dce727e3...ddfbd903, site IDa19d21c1-a18d-43ec-b1d0-267aa3be516fvisible in Wordfence Central. Key stored in 1P asKMA Wordfence License Key (free).ping_api_keyreturnedok:1confirming cloud signature feed active. Context: G&M has mostly migrated to Cloudflare; KMA is a client-only Wordfence install. Two other G&M sites still on WF (buildwithfoster, omahaplayhouse). - 2FA STRICTLY ENFORCED ✅ (2026-04-23 ~11:45 AM CDT) — Wordfence Login Security.
require_2fa.administrator=true,require_2fa.editor=true. Grace period REMOVED (grace_period_enabled=false,grace_period=0) per Eric's directive — no soft-enforcement.allow_remember=true(30-day device trust after enrollment),enable_login_history=true. Covers all 5 users. - gmlaunch (Eric) 2FA paired ✅ (2026-04-23 ~11:55 AM CDT) — TOTP secret + 5 recovery codes stored in 1P
KMA WP Admin — gmlaunch (G&M). Sign-in now auto-fills TOTP from 1P. Other 4 users still unenrolled — will enroll on their next login.
Emails — STATUS BY DRAFT/SENT (updated 2026-04-23 morning)
| Status | Where | |
|---|---|---|
| "First ack" (2026-04-22 3:57 PM) | ✅ SENT | Thread 19db676dd49ad9d8 |
| "Detailed explanation + $225" (2026-04-22 4:48 PM) | ✅ SENT | Same thread |
| "You're welcome, proceeding" (2026-04-22 5:15 PM) | ✅ SENT | Same thread |
| "1PW heads-up + user trim" (2026-04-22 5:45 PM) | ✅ SENT | Same thread |
| Liz 1P share link + login | ✅ SENT 10:25 AM | Fresh thread, to LWarren@kmaofny.com only |
| Chris Quinn 1P share link + login | ✅ SENT 10:25 AM | Fresh thread, to cquinn@kmaofny.com only |
| Jessica Wyman 1P share link + login | ✅ SENT 10:25 AM | Fresh thread, to jessica@wymanprojects.com only |
| Comprehensive cleanup summary + AECOM block | ✅ SENT 10:25 AM | Existing thread 19db676dd49ad9d8, full CC group |
| Liz reply — Wordfence notifications | ✅ SENT 2026-04-23 PM | Reply on thread 19db676dd49ad9d8 asking Liz which email to route WF alerts to |
| Roberto reply — MFA confirmation | ✅ SENT 2026-04-23 PM | Reply on thread 19db676dd49ad9d8 confirming MFA strict enforcement |
| 2FA setup instructions (strict) | ✅ SENT 2026-04-23 PM | Fresh thread to Liz + Chris + Jessica, post-enforcement instructions |
| Roberto reply — "Can't verify sender" | ✅ SENT 2026-04-24 ~1:40 PM CDT | Thread 19dc01580e396e51. Diagnosed DMARC/Salesforce-auth gap. Asked for .eml forward so we can read Authentication-Results. Draft ID r-8062856250348610966. |
| Roberto reply #2 — Salesforce DKIM instructions | ✅ SENT 2026-04-24 ~3:45 PM CDT | Same thread 19dc01580e396e51. Click-by-click DKIM setup for whoever runs Crosscheck's Salesforce. Draft ID r4850295149448935139. Roberto replied approving the proper fix; Liz confirmed Jack will forward. |
| Liz — scope/billing ask for +1-2 hours | ✅ SENT 2026-04-24 2:35 PM CDT | Thread 19db676dd49ad9d8. Msg 19dc0fdb89b908f8. Liz approved same day: "Yes, the additional time is approved." (msg 19dc117e65975d11). |
| Liz — weekend-risk reassurance | ✅ SENT 2026-04-24 3:06 PM CDT | Same thread. Msg 19dc1198ad1c5e72. Told Liz Monday pickup is fine; most business email pauses over the weekend. Liz: "Got it — thank you." (msg 19dc11efe7eed5f7). |
| Jack — acknowledgment of .eml | ✅ SENT 2026-04-24 2:31 PM CDT | Fresh thread 19dc0ec15a7a4847 ("Unverified Email Notice"). Short "Received. Thank you, Jack." reply to Jack's forwarded .eml. |
Pending Eric actions (updated 2026-04-23 PM)
- ~~Flywheel dashboard "Shuffle Secret Keys"~~ (dropped per Eric 2026-04-23 PM)
- ~~Paste ACF Extended Pro real license key~~ (dropped per Eric 2026-04-23 PM)
- ~~Verify DMARC propagation~~ ✅ Verified 2026-04-23 PM —
p=quarantineresolving cleanly from Google (8.8.8.8), Cloudflare (1.1.1.1), and Quad9 (9.9.9.9). - ~~Wire up Wordfence alert emails~~ ✅ Done 2026-04-23 PM — Liz replied requesting cquinn@kmaofny.com, lwarren@kmaofny.com, support@neighbortech.net.
alertEmailsset inwp_wfconfig; verified viaSELECT. Final draft in Gmail:r121076157454780519(proper HTML with<p>tags — send this one). Two stale plain-text drafts must be trashed manually:r-6270618732975746693andr-3703222724441272529(API can't delete drafts). - Monitor Liz/Chris/Jessica's first logins — each will be forced to pair TOTP on first access. Reply to inbound questions as they come.
Wordfence scan — COMPLETE (2026-04-23 PM)
Final wp_wfissues state: 3 findings total, all status=ignoreP (permanent-ignore), all severity=75 (Low, not High — earlier README entries mislabeled these):
- wp-admin/includes/file.php — WordPress core file modified
- wp-admin/includes/upgrade.php — WordPress core file modified
- wp-settings.php — WordPress core file modified
All three are legitimate Flywheel platform modifications (documented in commit 38f7691). Already dismissed in DB; won't re-flag on future scans.
Completed 2026-04-23 PM (today's session)
- Wordfence: free license registered + installed + connected to Central (site ID
a19d21c1-a18d-43ec-b1d0-267aa3be516f) - 2FA: strictly enforced (grace period removed) for admin + editor roles via Wordfence LS
- gmlaunch (Eric) 2FA fully paired via 1P — TOTP + 5 recovery codes stored in G&M vault
- All 5 WP users now represented in 1P G&M vault (gmlaunch, KM_Associates added today)
- Masterdoc Client Logins: all 5 users, Wordfence row with API key, section formatting fixed
- Emails sent: reply to Liz (alerts Q), reply to Roberto (MFA confirmation), new 2FA setup instructions to Liz/Chris/Jessica
- Investigated first Wordfence scan's 3 "High" findings — all three are benign Flywheel platform patches, not compromise residue.
wp-settings.php(Flywheel.fw-config.phpinclude),wp-admin/includes/upgrade.php(Flywheel commented outwp_new_blog_notification),wp-admin/includes/file.php(byte-identical, WF false positive). Safe to "Ignore Always" in WF UI once scan completes.
Pending — check back later today
- Wordfence scan was mid-run when parked (~3,200+ files scanned, ~2.5/sec pace). Eric to ping "check the Wordfence scan" later so Claude can pull final findings from
wp_wfissues, confirm no new issues beyond the 3 Flywheel mods, and mark those 3 as "Ignore Always" in DB.
Phase 3 still open (tonight/tomorrow)
- Blocklist check: MxToolbox, Spamhaus, Barracuda. Submit removal requests wherever kmaofny.com is flagged.
- SEO cleanup: submit URL removal requests via Google Search Console for the 3 deleted spam post URLs (if Google indexed them during the April 17-22 window).
- Graduate DMARC to
p=rejectafter 1-2 weeks of clean monitoring underp=quarantine.
Phase 4 progress (2026-04-23 late morning)
✅ 2FA STRICTLY enforced on production (via Wordfence Login Security). No separate plugin — Wordfence's bundled Login Security module handles it.
- require_2fa.administrator = true
- require_2fa.editor = true (covers jwyman)
- grace_period_enabled = false (no grace period — strict enforcement per Eric 2026-04-23)
- grace_period = 0
- allow_remember = true (30-day device trust after enrollment)
- enable_login_history = true (security monitoring)
- Enrolled users: 0 — each user MUST pair a TOTP app on next login before reaching admin. No bypass.
- 5 users covered: 4 admins (KM_Associates, gmlaunch, ewarren, cquinn) + 1 editor (jwyman)
- Wordfence firewall: learning-mode (normal default; auto-graduates to blocking after ~1 week)
- Login brute-force lockout: maxFailures=20, lockoutMins=240 (Wordfence defaults — could be tightened to 5/60 later)
- Gmail draft created to Liz + Chris + Jessica with strict-enforcement 2FA setup instructions. Old draft (with grace-period language) superseded — Eric should send the newer one and delete the older. Draft IDs: new r4046085531964448619, old r-6983825105055880573 (delete).
✅ Wordfence free license registered, installed on prod, AND connected to Wordfence Central.
- License pulled from G&M Wordfence account (eric@grainandmortar.com, 2FA enabled) via wordfence.com/licenses → "Get a Free License"
- API key saved in 1P: KMA Wordfence License Key (free) in G&M vault
- Installed via wfConfig::set("apiKey", ...) + keyType=free
- ping_api_key returned ok:1 with live dashboard payload
- Connected to Wordfence Central — site ID a19d21c1-a18d-43ec-b1d0-267aa3be516f. Now visible in Central dashboard alongside buildwithfoster.com and omahaplayhouse.com (the other two G&M sites still on WF — the rest have migrated to Cloudflare). KMA is a client-only WF install, not G&M-standard.
- Note for future G&M Wordfence logins: account has 2FA enabled via TOTP on Eric's authenticator; need the 6-digit code (or recovery code) to log in.
Phase 4 still open
- (none remaining from the 2FA + Wordfence scope)
- Audit other G&M sites for the weak
.CLIENT<service>158!password pattern (Flywheel + Earthlink entries on the Masterdoc use this convention). - Audit other G&M sites for
wp-file-managerinstalls (same CVE). - Add KMA to G&M Maintenance Portal for monthly plugin version monitoring.
- Log all engagement hours to Harvest at $225 rate.
Reference material (for resuming)
- Live site: https://kmaofny.com (HTTP 200 at park time)
- SSH:
ssh kmaofny+km-associates@ssh.getflywheel.com(site files at/www/) - Masterdoc: KM Associates (KMA) · Master — credentials, organized "Client Logins" tab
- Email thread with Liz: Gmail thread ID
19db676dd49ad9d8(subject "Compromised kmaofny.com Website") - New skill created during this session:
~/.claude/skills/plugins/SKILL.md(per-vendor premium plugin reference) - New agent created during this session:
~/.claude/agents/project-intake.md(new-project orchestration) - Passwords (v2):
forensic-workspace/NEW-PASSWORDS-v2-20260422.txt(mode 600) - Passwords (v3, current):
forensic-workspace/NEW-PASSWORDS-v3-20260422.txt(mode 600) — contains Liz/Chris/Jessica credentials, now mirrored in 1P G&M vault - 1P bridge trick: to use Eric's personal 1P session instead of the read-only Claude Bot service account, prefix commands with
unset OP_SERVICE_ACCOUNT_TOKEN &&in a single Bash call. Falls back to desktop-app auth, exposes G&M / Personal / Ventures vaults with write access. - Forensic evidence:
forensic-workspace/(DB dump, malicious files, rogue themes tarball, spam posts tarball, screenshots)
First move when resuming tonight
Open ~/.claude/project-notes/km-associates/README.md, scroll to this "Session parked" section. Start with the "Pending Eric actions" list.
Open Questions for Eric
- Where is kmaofny.com hosted? (Flywheel, legacy shared host, something else?)
- Do we have existing SSH + WP admin credentials in 1Password from the prior engagement?
- Is there a Basecamp project for KMA already, or do we need to create one?
- Is this billable as T&M security work, or a fixed-scope engagement?
- What's the prior G&M engagement history with them? (The theme looks custom-built — did we build it originally?)