Security (7 fixes):
- SEC-001: Use wpdb->prepare() for COUNT query in REST API
- SEC-002: Set httponly=true on session cookie to prevent XSS
- SEC-003: Mask API keys in admin settings (show last 4 chars only)
- SEC-004: Check capability before nonce in manual fetch handler
- SEC-005: Validate redirect domains to prevent open redirects
- SEC-006: Escape all article fields before REST API response
- SEC-007: Add capability check to AJAX summary handler
Infrastructure (7 fixes):
- WPS-002: Clear cron schedule on plugin deactivation
- WPS-003: Bundle Chart.js locally instead of CDN without SRI
- WPS-004: Add uninstall.php for clean plugin removal
- DB-001: Add foreign key constraints with CASCADE deletes
- DB-002: Clamp pagination to valid page range
- CI-001: Use StrictHostKeyChecking=accept-new in deploy
- CI-002: Add security scanning step to CI pipeline
- CI-003: Add rollback workflow via workflow_dispatch
Performance & Quality (8 fixes):
- PHP-001: Validate widget $instance as array before access
- PHP-002: Return null from get_client_ip() for invalid IPs
- PHP-003: Process RSS feeds in chunked batches of 10
- PHP-004: Wire up clear_article_cache() in fetch cycle
- PERF-001: Cache source names in transient for strip_source_suffix
- PERF-002: Add server-side transient caching for REST API
- ARCH-002: Make LLM API URL configurable via WordPress options
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ssh-keyscan can fail transiently; since rsync already uses
StrictHostKeyChecking=no, the keyscan is best-effort only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The git-pull deploy failed silently because Hostinger doesn't have SSH
keys to access the private GitHub repo. Switched to rsync from the GH
Actions runner (same proven approach as the theme repo), which avoids
needing git access on the server entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hostinger GIT integration has a persistent backend bug that prevents using the correct plugin directory name. This replaces it with SSH-based deployment:
- Sync deploy branch with clean plugin runtime files
- SSH into Hostinger and git pull deploy branch directly
- Purge LiteSpeed cache after successful deploy
Hostinger's git pull fails with divergent branches when force-pushing
orphan commits. Switch to a linear commit strategy: checkout the
existing deploy branch, sync plugin files from main, and push normally.
This keeps Hostinger's git pull fast-forward compatible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace broken SCP/SSH deploy (blocked by Hostinger firewall) with a
deploy-branch strategy. After CI passes, GitHub Actions pushes only
plugin files to the 'deploy' branch. Hostinger's built-in GIT
integration watches this branch via webhook and auto-pulls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FTP deploy timed out due to Hostinger blocking FTPS from external CI
servers. Switched to appleboy/scp-action using SSH on port 65002,
which is more reliable for CI/CD pipelines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deploy workflow references CI via `uses: ./.github/workflows/ci.yml`
which requires the CI workflow to have an `on: workflow_call` trigger.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces GitHub Actions workflows for continuous integration and
deployment. CI runs PHP linting (7.4, 8.1, 8.3), PHPCS with WordPress
coding standards, PHPUnit tests across PHP versions, and JS syntax
checks. Deployment to Hostinger via FTPS triggers on push to main
after all CI checks pass, with optional LiteSpeed cache purge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>