A WordPress plugin that connects your wp-admin to the Sudo-WP GitHub organization, allowing you to search, install, and update security-patched plugin forks directly from the dashboard, without downloading or renaming ZIP files manually.
Installing a plugin from GitHub has always had a friction problem. GitHub archive ZIPs extract to a folder named repo-branch rather than repo. WordPress expects the folder to match the plugin slug exactly. Without a manual rename, the plugin either fails to activate or activates under the wrong path.
Keeping those forks up to date is another manual step: check GitHub for new tags, download the ZIP, remove the old plugin directory, upload and rename the new one.
SudoWP Hub removes both steps entirely.
- Presents all Sudo-WP organization repositories as native-looking plugin cards inside wp-admin, using the same CSS layout as the WordPress Add New plugin screen
- Handles installation via
WP_Upgrader, the same internal class WordPress uses for all plugin installs - Intercepts the
upgrader_source_selectionfilter to rename the extracted folder fromrepo-branchto the correct slug, server-side, before WordPress finalizes the install - Caches GitHub API search results for 5 minutes per unique query to reduce API usage
- Supports toggling between plugins and themes
- Dedicated Updates page under the SudoWP Hub menu, with a tab on the native Plugins list table
- Compares installed plugin Version headers against the latest GitHub tag (highest semver from up to 20 tags)
- Per-row "Update now" links and bulk "Update Selected" functionality
- Uses
Plugin_Upgrader->upgrade()via transient injection, the same code path WordPress core uses for all plugin updates - Automatic daily background update check via wp-cron, with self-healing on admin_init
- Re-activates plugins automatically after a successful update
- Per-slug tag caching (12-hour TTL) with rate-limit-aware caching (5-minute TTL for 403/429 responses)
- Pending update count badge on the menu item
SudoWP Hub connects to an external API and performs plugin installations. The following security decisions apply:
SSRF prevention. All ZIP download URLs are constructed server-side from validated parts: fixed scheme (https), fixed host (github.com), fixed organization prefix (/Sudo-WP/), a validated slug, and a validated branch or tag segment. Both validate_github_url() (branch-based installs) and validate_github_tag_url() (tag-based updates) reject any URL containing query strings, fragments, or embedded credentials. Client-supplied URLs are re-validated before WP_Upgrader is invoked.
Capability gating. All AJAX handlers require appropriate capabilities: install_plugins for search and install, update_plugins for update operations. The admin menu pages require install_plugins to render. Unauthenticated and low-privilege users cannot reach any of the plugin's functionality.
Nonce handling. Nonces are passed to JavaScript exclusively via wp_localize_script() on plugin-owned script handles (sudowp-hub-admin and sudowp-hub-updates). They are never embedded as string literals in inline JavaScript.
Per-user rate limiting. Transient-based rate limiting is enforced on all AJAX endpoints: 2-second window on search and install, 30-second window on update operations. This prevents rapid repeated requests that could exhaust the GitHub API rate limit from the server's IP.
Token sanitization. GitHub Personal Access Tokens are sanitized to [a-zA-Z0-9_-] only, trimmed to 255 characters. The Authorization header uses the Bearer prefix per current GitHub API documentation. A sentinel field distinguishes deliberate token clearing from an empty form submission.
Slug validation. The is_valid_slug() helper restricts slugs to [a-zA-Z0-9_-] before any slug value is used in a regex or filesystem path. This runs before URL validation to prevent ReDoS.
| Requirement | Minimum |
|---|---|
| WordPress | 6.0 |
| PHP | 7.4 |
| GitHub API | Public access (no token required; token recommended) |
- Download the latest release ZIP from the Releases tab.
- In wp-admin, go to Plugins - Add New - Upload Plugin.
- Upload the ZIP and activate.
- A "SudoWP Hub" item will appear in the admin menu with Browse and Updates subpages.
Optional: Add a GitHub Personal Access Token (public_repo scope) in the Configuration section at the bottom of the Browse page. Without a token, the GitHub API allows approximately 10 search requests per minute from the server's IP. With a token, the limit rises to 5,000 requests per hour. A token is strongly recommended for the Updates page, which makes multiple API calls per page load.
To add a token:
- Go to GitHub - Settings - Developer settings - Personal access tokens - Fine-grained tokens (or classic tokens with
public_reposcope). - Generate a token.
- Paste it into the Configuration section on the SudoWP Hub Browse page and click Save Settings.
To clear a saved token: leave the field empty and click Save Settings. The plugin uses a sentinel field to distinguish deliberate clearing from an accidental empty submission.
- No file integrity verification. Hub trusts the GitHub API over HTTPS and relies on the same
WP_Upgraderpipeline WordPress uses for all installs and updates. - Pagination not yet implemented. The GitHub API caps results at 100 per query. If the Sudo-WP organization grows beyond 100 repositories, results will be silently truncated. A result count indicator and pagination are planned for a future release.
- Activation URL. After a fresh install, the Activate link points to
plugins.phprather than a direct activation URL. This is a known UX gap; direct activation URL construction is planned.
Single-file plugin. All logic lives in sudowp-hub.php inside the SudoWP_Hub class using a singleton pattern. No external dependencies. No build step. No Composer.
Key methods:
| Method | Purpose |
|---|---|
init() |
Registers all hooks |
ajax_search() |
Handles GitHub API search, caches results as transients |
ajax_install() |
Handles plugin/theme installation via WP_Upgrader |
ajax_check_updates() |
Force-refreshes update data transients |
ajax_run_updates() |
Per-row and bulk plugin updates via Plugin_Upgrader->upgrade() |
build_results_html() |
Renders plugin card HTML from GitHub API items |
validate_github_url() |
SSRF prevention for branch-based ZIP URLs |
validate_github_tag_url() |
SSRF prevention for tag-based ZIP URLs (updates) |
rename_github_source() |
Fixes GitHub ZIP folder naming via upgrader_source_selection filter |
get_sudowp_org_repos() |
Fetches all repo names from the Sudo-WP org, cached 12h |
get_repo_latest_version() |
Fetches tags, returns highest semver as raw + stripped version |
get_sudowp_update_data() |
Matches installed plugins to GitHub versions, cached 12h |
render_updates_page() |
Renders the Updates admin table with bulk and per-row actions |
clear_all_update_caches() |
Clears all update transients including per-slug tag caches |
sanitize_token() |
Sanitizes and stores GitHub PAT; handles clearing via sentinel field |
get_inline_js() |
Returns Browse page JavaScript as a string |
get_updates_inline_js() |
Returns Updates page JavaScript as a string |
JavaScript is inline, passed via wp_add_inline_script() on registered handles. The SudoWPHub and SudoWPHubUpdates JavaScript objects are populated via wp_localize_script() with nonces, the AJAX URL, and i18n strings.
- Fix: Plugins that were active before an update are now re-activated automatically after the upgrade completes, matching WordPress core behavior. Multisite network-wide activation is preserved.
- Cleanup: Removed all diagnostic logging. Update mechanism confirmed working correctly.
- Fix: Plugin updates now use
Plugin_Upgrader->upgrade()by injecting the GitHub package URL into theupdate_pluginstransient. This is the same code path WordPress core uses for all plugin updates, ensuring correct filesystem handling, plugin deactivation before file replacement, and temp backup support.
- Fix: Replaced manual delete-then-install pattern with
Plugin_Upgrader->run()usingclear_destinationfor correct live server behavior.
- Fix: Updates page no longer shows "Update available" immediately after a successful update. Added
wp_clean_plugins_cache()toclear_all_update_caches()so WordPress re-reads plugin headers from disk.
- Fix: Update install no longer fails when the GitHub org repos API returns empty. Falls back to scanning installed plugins for sudowp-prefixed folders.
- Fix: Rate-limited responses (HTTP 403/429) from GitHub tag API are now cached for 5 minutes, breaking the retry loop that exhausted the unauthenticated rate limit.
- UX: Updates page shows a warning notice with a link to settings when no GitHub token is configured.
- Fix: Tag lookup now caches results per slug (12-hour transient), preventing rate-limit exhaustion when multiple SudoWP plugins are installed.
- Fix: API failures no longer cache a false-negative; only genuine "no tags" results are cached.
- Refactor: All cache-clearing paths now use
clear_all_update_caches()to consistently flush per-slug tag caches alongside the main update data.
- Feature: Automatic daily background update check via wp-cron, modeled on WordPress core update behavior.
- Feature: Updates page now shows time of next scheduled automatic check alongside the last checked timestamp.
- Scheduled event is registered on activation, cleared on deactivation and uninstall, and self-heals on admin_init if missing.
- Fix:
get_repo_latest_version()now fetches up to 20 tags and selects the highest by semver rather than trusting GitHub's creation-date ordering.
- Feature: Rewritten Updates page with per-row "Update now" links and bulk "Update Selected" functionality.
- Feature: Updates tab on native Plugins list table via
views_pluginsfilter, with pending count badge. - Feature: New
ajax_run_updates()AJAX handler with nonce, capability, and 30-second rate limiting. - Feature: Tag-based ZIP URL construction for updates, with new
validate_github_tag_url()validation method.
- Feature: New "Updates" submenu page showing installed SudoWP plugins with their update status compared to GitHub.
- Feature: Automatic version comparison between installed plugin headers and latest GitHub tags.
- Feature: "Check for Updates" button with rate limiting and 12-hour transient cache.
- Feature: Update count badge on the menu item when updates are pending.
- Refactor: Extracted shared GitHub API request args into reusable
get_github_api_args()method.
- Security: Type-aware capability check (install_themes vs install_plugins) on search and install handlers.
- Security: Applied
wp_kses_postoutput escaping on rendered card HTML. - Security: Applied
wp_unslashon all POST data access. - Security: Added
uninstall.phpwith token and transient cleanup on plugin deletion.
- Renamed admin menu item and page title from "SudoWP Store" to "SudoWP Hub".
- Feature: Plugin cards now show installed state. Active plugins show an "Active" badge. Inactive installed plugins show an "Activate" button. Uninstalled plugins show "Install Now".
- Feature: Theme cards use the same installed-state detection with the same three states.
- Refactor: GitHub API results cached as raw item arrays rather than rendered HTML, so installed-state detection always reflects current state at render time.
- Removed unused variable in
ajax_search()identified during code review (cosmetic, no functional impact).
- Security: Added
install_pluginscapability check to AJAX search handler. - Security: Nonces moved to
wp_localize_script()from inline JS string literals. - Security: GitHub Authorization header updated from deprecated
tokentoBearer. - Security: Strict field-level validation of all GitHub API response items before rendering.
- Security: ZIP URLs constructed server-side; client-supplied URL re-validated before install.
- Security:
rename_github_source()returnsWP_Erroron filesystem failure instead of silently passing the original path. - Security:
validate_github_url()now rejects URLs with query strings, fragments, or embedded credentials. - Fix: Token can now be cleared by submitting an empty field (sentinel field logic).
- Fix: Token sanitization restricted to
[a-zA-Z0-9_-], max 255 characters. - Performance: Search results cached as WordPress transients for 5 minutes.
- Performance: Plugin registers its own script handle instead of piggybacking on WP core handles.
- Performance: GitHub API requests up to 100 results per page.
- Performance: Per-user AJAX rate limiting (2-second window) on both endpoints.
- Compliance:
load_plugin_textdomain()added; all strings wrapped in translation functions. - Compliance: All output uses context-appropriate escaping.
- UX: Install errors displayed inline;
alert()removed. - UX: External links include
rel="noopener noreferrer". - UX: GitHub API HTTP 403, 429, and non-200 responses handled with distinct messages.
- Initial release.
GPL-2.0-or-later. See LICENSE.
- SudoWP.com - Project home and blog
- Sudo-WP GitHub Organization - All patched plugin forks
- WPRepublic.com - WordPress security research