Title: Scalability improvements
Last modified: July 31, 2026

---

# Scalability improvements

 *  [teeboy4real](https://wordpress.org/support/users/teeboy4real/)
 * (@teeboy4real)
 * [1 week, 1 day ago](https://wordpress.org/support/topic/scalability-improvements/)
 * Hi
 * I am running a membership platform and I intend to have thousands/millions of
   users, so I used **claude **to review and audit your plugin and the response 
   is stated below.
 * **CLAUDE RESPONSE**
 * I went through the full plugin — schema, models, WebAuthn crypto core, AJAX handlers,
   and background jobs. Overall it’s a solid, professionally-built plugin (proper
   prepared statements throughout, real WebAuthn clone-detection via signature counter,
   nonce + capability checks, rate limiting). But at “millions of users” scale, 
   there are a few real problems, not just theoretical ones.
 * **Security — mostly good, one real gap**
    - All queries use `$wpdb->prepare()` correctly; the paginate/filter builder 
      whitelists column names before interpolating them, which is the right pattern.
    - Nonce checks (`verify_nonce`), capability checks (`current_user_can`), and
      login rate limiting are present on every AJAX endpoint.
    - Signature counter clone-detection is implemented correctly (`prevSignatureCnt
      >= signatureCounter` throws), which is the part plugins most often get wrong.
    - **IP trust gap**: `get_ip_address()` defaults `secure_passkeys_trust_proxy_headers`
      to `false`, so it reads `REMOTE_ADDR` only. Since NigerPress sits behind Cloudflare,`
      REMOTE_ADDR` at your OpenLiteSpeed layer will be Cloudflare’s edge IP unless
      you’ve configured real-IP restoration (mod_cloudflare / trusted proxy headers
      in CyberPanel). If that’s not set up, every visitor shares the same “IP” from
      this plugin’s point of view — meaning the rate limiter effectively rate-limits
      _all_ users together, and one abusive user can lock out logins for everyone
      behind that edge node. Worth checking your OpenLiteSpeed real-IP config, then
      adding a filter to trust `X-Forwarded-For`/`CF-Connecting-IP` (the plugin 
      already supports this via the `secure_passkeys_trust_proxy_headers` filter—
      you just need to enable it and point it at the right header for your CF setup).
    - `credential_id` is stored `VARCHAR(255)` after base64-encoding the raw binary
      ID. Most platform authenticators (Touch ID, Windows Hello, Android) produce
      short IDs, but some FIDO2 security keys (YubiKeys with resident/discoverable
      credentials) can produce IDs long enough that base64 pushes past 255 chars.
      On non-strict MySQL this truncates silently; on strict mode it throws. It’s
      an edge case, but at millions-of-users scale it’s a matter of _when_, not 
      _if_, someone hits it. Bumping the column to `VARCHAR(400)` or `TEXT` with
      a prefix index would remove the risk entirely.
 * **Scalability — this is where the real concerns are**
    - **Unbounded challenge table growth by default.** Every login/registration 
      attempt inserts a row into `secure_passkeys_challenges`, but the cleanup cron
      only runs `if ($challenge_cleanup_days > 0)` — and the default setting is `
      0` (disabled). At scale, this table grows forever unless an admin manually
      opts into cleanup. This is the single biggest scale risk in the plugin. I’d
      recommend forcing a sane default (e.g. 30 days) rather than off.
    - **Cleanup query itself doesn’t scale.** `delete_old_records()` uses `DATEDIFF(
      NOW(), created_at) >= %d` — wrapping the column in a function makes it non-
      sargable, so MySQL can’t use an index even if one existed (and neither `created_at`
      column has one). On a multi-million-row table this becomes a full table scan
      holding row locks for a long single transaction — risky for replication lag
      and for anything else hitting that table concurrently.
    - **No batching/chunking on delete.** It’s one unbounded `DELETE FROM ... WHERE...`
      rather than deleting in batches (e.g. `LIMIT 1000` loops). On huge tables 
      this can cause long-running locks and InnoDB undo-log bloat.
    - **Plain wp-cron, not Action Scheduler.** This is inconsistent with how you’ve
      built out your other plugins — wp-cron only fires on front-end traffic and
      has no locking/retry semantics, so on a high-traffic multi-tier setup the 
      cleanup job can be skipped, double-run, or drift. Given you already use Action
      Scheduler elsewhere (Woocommerce), it’d be a natural, low-effort swap here
      too.
    - **Uncached admin dashboard aggregates.** The overview screen runs `COUNT(*)`
      on all three tables plus a `JOIN` for unique-user-count plus a `GROUP BY aaguid`(
      unindexed column) across the whole webauthns table, on every page load, with
      no transient/object-cache layer. At millions of credentials this becomes a
      genuinely slow admin page. Since you’re running Redis Object Cache Pro already,
      wrapping these in a short-lived transient (1–5 min) would cost nothing and
      fix it.
 * **What already scales fine:** per-user lookups (`user_id` is indexed on all three
   tables), the credential-id unique constraint prevents duplicate-registration 
   races at the DB layer even though there’s also an app-level check first, and 
   rate limiting via transients will ride on your Redis object cache automatically.
 * Really hope this suggestions can be implemented especially with the use of action
   scheduler, Note that action scheduler is already bundled with woocommerce.
 * Thanks

You must be [logged in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fwordpress.org%2Fsupport%2Ftopic%2Fscalability-improvements%2F%3Foutput_format%3Dmd&locale=en_US)
to reply to this topic.

 * ![](https://ps.w.org/secure-passkeys/assets/icon-256x256.png?rev=3262385)
 * [Secure Passkeys](https://wordpress.org/plugins/secure-passkeys/)
 * [Frequently Asked Questions](https://wordpress.org/plugins/secure-passkeys/#faq)
 * [Support Threads](https://wordpress.org/support/plugin/secure-passkeys/)
 * [Active Topics](https://wordpress.org/support/plugin/secure-passkeys/active/)
 * [Unresolved Topics](https://wordpress.org/support/plugin/secure-passkeys/unresolved/)
 * [Reviews](https://wordpress.org/support/plugin/secure-passkeys/reviews/)

 * 0 replies
 * 1 participant
 * Last reply from: [teeboy4real](https://wordpress.org/support/users/teeboy4real/)
 * Last activity: [1 week, 1 day ago](https://wordpress.org/support/topic/scalability-improvements/)
 * Status: not resolved