Skip to content

security: trusted-proxy rate limiting, atomic restore, encrypted webhook secret#10

Merged
altugank merged 1 commit into
mainfrom
security/proxy-ip-atomic-restore-secret-at-rest
Jun 9, 2026
Merged

security: trusted-proxy rate limiting, atomic restore, encrypted webhook secret#10
altugank merged 1 commit into
mainfrom
security/proxy-ip-atomic-restore-secret-at-rest

Conversation

@altugank

@altugank altugank commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Follow-ups from the live security review. Each change was verified against a real WordPress 7.0 / PHP 8.2 install (code-level harness + live HTTP).

1. Trusted-proxy client IP for rate limiting

The webhook rate limiter keyed on REMOTE_ADDR, so behind a CDN/reverse proxy every visitor shared one bucket.

  • New WP_Puller_Client_IP resolves the real client IP. Forwarding headers (CF-Connecting-IP, True-Client-IP, X-Forwarded-For) are trusted only when REMOTE_ADDR is itself a trusted proxy — so a direct attacker can't spoof them to dodge the limiter.
  • Cloudflare supported out of the box (ranges fetched weekly via wp_safe_remote_get + hard-coded fallback). Loopback/RFC1918 covered for a site's own nginx/HAProxy.
  • Extensible to Sucuri / Akamai / custom LBs via wp_puller_trusted_proxies and wp_puller_client_ip_headers filters. Full IPv4 + IPv6 CIDR matching.

Verified: spoofed X-Forwarded-For/CF-Connecting-IP from an untrusted source is ignored; Cloudflare/Akamai/private-proxy paths resolve correctly; rate limiter buckets per real client and can't be split by header spoofing.

2. Atomic theme restore

restore_backup() deleted the live theme before copying the backup — a mid-copy failure left the site themeless.

  • Now stages the restored copy in a dot-prefixed sibling dir (ignored by the theme scanner), then swaps via fast rename()s with rollback.

Verified: happy path restores correctly; a forced failure (read-only parent) returns a WP_Error with the live theme fully intact and no leftover temp dirs.

3. Webhook secret encrypted at rest

Per review finding #1, but using the WordPress-idiomatic mechanism rather than anything custom: the existing salt-derived WP_Puller::encrypt() (same as the GitHub PAT).

  • Backward compatible: legacy plaintext secrets are read transparently and upgraded to encrypted (v2:) on the next save or admin page view.
  • Still displayed decrypted on the settings page for GitHub setup.

Verified: legacy plaintext verifies; lazy migration to v2: works and keeps verifying; fresh secret stored encrypted (raw DB value is v2:…, no plaintext); live signed webhook returns 200.

Testing

  • php -l clean across the plugin; CI green.
  • ~30 code-level assertions + live HTTP webhook tests, all passing.

Notes

  • No release wp-puller.zip rebuild (handled separately, per CONTRIBUTING).
  • New code tagged @since 1.0.8.

…ook secret

Follow-ups from the security review, each verified in a live WP 7.0 install.

1. Trusted-proxy client IP (rate limiting was keyed on REMOTE_ADDR):
   - New WP_Puller_Client_IP resolves the real client behind reverse proxies
     and CDNs. Forwarding headers (CF-Connecting-IP, True-Client-IP,
     X-Forwarded-For) are honoured ONLY when REMOTE_ADDR is itself a trusted
     proxy, so they cannot be spoofed to evade the limiter.
   - Cloudflare ranges supported out of the box (fetched weekly + hard-coded
     fallback); loopback/RFC1918 covered for a site's own nginx/HAProxy.
   - Extensible via `wp_puller_trusted_proxies` / `wp_puller_client_ip_headers`
     for Sucuri, Akamai, custom load balancers, etc. IPv4 + IPv6 CIDR matching.

2. Atomic theme restore (restore_backup deleted the live theme before copying):
   - Stage the restored copy in a dot-prefixed sibling dir, then swap via fast
     renames with rollback. A failed restore can no longer brick the theme.

3. Webhook secret encrypted at rest:
   - Stored via the existing WordPress salt-derived WP_Puller::encrypt() (the
     same mechanism as the GitHub PAT). Reads are backward compatible with
     legacy plaintext secrets, which are transparently upgraded on next save
     or admin page view. The secret is still shown decrypted for GitHub setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@altugank altugank merged commit d0cba68 into main Jun 9, 2026
6 checks passed
@altugank altugank deleted the security/proxy-ip-atomic-restore-secret-at-rest branch June 9, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant