WP Hook Permissions
WP Hook Permissions is a must-use plugin that brings full transparency to the WordPress hook system. It tracks every `add_action()` and `add_filter()` call made by installed plugins and themes, attributes each callback to its source, and lets administrators allow or deny individual hooks per plugin — all without touching plugin code.
by Smartlogix · github.com/namithj/wp-hook-permissions · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/namithj/wp-hook-permissions/archive/refs/heads/main.zipTracks, attributes, and permission-gates WordPress hook callbacks at runtime.
[!WARNING] Proof of Concept
This plugin is an exploratory implementation intended to surface ideas and spark discussion around giving WordPress administrators granular visibility and control over hook callbacks.
The goal is to demonstrate what a more elaborate and more secure implementation could look like, and to invite feedback from the WordPress community on the approach, API surface, and threat model before investing in a fuller solution.
Overview
WP Hook Permissions is a must-use plugin that gives site administrators full visibility and control over the WordPress hook system. It passively tracks every add_action() and add_filter() call, attributes each callback to its source plugin or theme, and lets you allow or deny hooks on a per-plugin basis — without touching any plugin code.
Key Features
| Feature | Description |
|---|---|
| Runtime tracking | Captures hook registrations on every request via a passive all hook listener |
| Permission gating | Removes denied callbacks before they run; fail-open by default |
| Activation review modal | Intercepts the Activate button and shows a grouped hook table before going live |
| Update monitoring | Newly discovered hooks after a plugin update are denied by default until reviewed |
| Admin UI | Tools → Hook Permissions with per-hook Allow/Deny toggles, grouped by category |
| Static scanner | Tokenises PHP files to find hooks without executing code |
| Blocked-execution log | Rolling audit trail of removed callbacks (capped at 100 entries) |
permissions.json support |
Plugin authors can document hook intent; notes surface in the UI |
Installation
From a release zip (recommended)
- Download
wp-hook-permissions-<version>.zipfrom the Releases page. - Unzip and upload the
wp-hook-permissions/directory towp-content/mu-plugins/. - Place (or merge)
plugins.phpinwp-content/mu-plugins/— it auto-loads all MU plugins that follow the<slug>/<slug>.phpconvention. - Must-use plugins activate automatically; no manual activation needed.
From Git
cd wp-content/mu-plugins
git clone https://github.com/smartlogix/wp-hook-permissions.git wp-hook-permissions
Then add the universal loader if it doesn't exist:
# wp-content/mu-plugins/plugins.php
<?php
defined( 'ABSPATH' ) || exit;
foreach ( glob( __DIR__ . '/*', GLOB_ONLYDIR ) as $dir ) {
$file = $dir . '/' . basename( $dir ) . '.php';
if ( file_exists( $file ) ) { require_once $file; }
}
Directory Structure
wp-hook-permissions/
├── wp-hook-permissions.php # Plugin header + bootstrap
├── src/
│ ├── Plugin.php # Service container
│ ├── PermissionStore.php # Options-backed permission map
│ ├── ExecutionController.php
│ ├── HookTracker.php
│ ├── Logger.php
│ ├── UpdateMonitor.php
│ ├── Admin/
│ │ ├── ActivationGuard.php
│ │ ├── Assets.php
│ │ ├── Notices.php
│ │ └── UI.php
│ ├── Contracts/
│ ├── Scanner/
│ │ ├── RuntimeTracker.php
│ │ └── StaticScanner.php
│ └── Utils/
│ ├── CallbackHasher.php
│ ├── HookGrouper.php
│ ├── PermissionsJson.php
│ └── SourceResolver.php
├── assets/
│ ├── css/admin.css
│ ├── css/activation-guard.css
│ └── js/activation-guard.js
├── tests/
│ ├── bootstrap.php
│ ├── Unit/
│ ├── Integration/
│ └── Fixtures/
├── uninstall.php
├── composer.json
├── phpcs.xml.dist
└── phpunit.xml.dist
How It Works
Fail-open permission model
| Condition | Result |
|---|---|
| Plugin not yet in inventory | Allowed |
| Plugin known, hook not mapped | Allowed |
Explicit true stored |
Allowed |
Explicit false stored |
Blocked |
Only an explicit false in the permission store blocks a callback. This preserves all existing functionality until the administrator makes a deliberate decision.
Bypass hooks
The following core bootstrap hooks are never gated to prevent accidental site breakage:
muplugins_loaded, plugins_loaded, setup_theme, after_setup_theme, init, wp_loaded, shutdown, all
Activation guard flow
User clicks Activate
│
▼
JS intercepts link
│
▼
AJAX preflight → StaticScanner scans PHP files → returns grouped hook list
│
▼
Modal opens — user reviews Allow/Deny per hook
│
▼
"Accept & Activate" → AJAX saves permissions → server issues one-time token
│
▼
JS redirects to WP activation URL + token → server validates + consumes token
│
▼
WP activates plugin/theme normally
Development
Requirements
- PHP 7.4+
- Composer
Setup
cd wp-hook-permissions
composer install
Available scripts
| Command | Description |
|---|---|
composer phpcs |
Run PHP_CodeSniffer (WordPress-Extra + Docs + Core) |
composer phpcbf |
Auto-fix PHPCS violations |
composer phpunit |
Run PHPUnit test suite |
composer phpstan |
Run PHPStan static analysis |
Testing
composer phpunit
# Run a single suite:
vendor/bin/phpunit --testsuite Unit
vendor/bin/phpunit --testsuite Integration
permissions.json
Plugin authors can ship a permissions.json at their plugin root to document hook intent. Descriptions appear as notes in the Hook Permissions UI and the activation modal. This file never auto-grants permissions.
{
"init": "Registers custom post types and taxonomies.",
"admin_init": "Loads plugin settings page."
}
Hooks & Filters
| Hook | Type | Description |
|---|---|---|
wphp/loaded |
action | Fires after the plugin has fully booted |
wphp/hook_groups |
filter | Override/extend the hook → group mapping used in the UI |
Changelog
0.1.0
- Initial release
License
GPL-2.0-or-later — see LICENSE.