Troubleshooting
Security package issues and fixes.
Nonce Issues
"Invalid nonce" on valid form
Problem: Form submission fails nonce verification.
Cause: Action name mismatch or nonce expired.
Fix: Ensure action names match exactly:
// Form generation
$token = $nonce->create('save_settings');
echo '<input type="hidden" name="_nonce" value="' . esc_attr($token) . '">';
// Verification (must match 'save_settings')
$nonce->verify($_POST['_nonce'], 'save_settings');
Nonce valid for wrong plugin
Problem: Plugin A accepts nonce from Plugin B.
Cause: Both plugins use same action name without prefixing.
Fix: Use context-prefixed Nonce class:
// Plugin A: creates "plugina_save" action
$nonceA = new Nonce($contextA);
$tokenA = $nonceA->create('save');
// Plugin B: creates "pluginb_save" action
$nonceB = new Nonce($contextB);
$nonceB->verify($tokenA, 'save'); // Fails (different prefix)
Nonce expires too quickly
Problem: Form valid for < 12 hours.
Cause: WordPress nonces have 24-hour lifetime (12h full + 12h grace).
Fix: This is expected behavior. For longer forms:
// Refresh nonce via AJAX
$hooks->wpAction('wp_ajax_refresh_nonce', function () use ($nonce) {
wp_send_json_success([
'nonce' => $nonce->create('save_settings'),
]);
});
Gate Issues
Capability check always fails
Problem: $gate->can('manage_options') returns false for admin.
Cause: Called before init when user not loaded.
Fix: Check capabilities after WordPress loads user:
$hooks->wpAction('init', function () use ($gate) {
if ($gate->can('manage_options')) {
// Now works
}
});
Custom capability not recognized
Problem: $gate->can('myplugin_edit') always false.
Cause: Custom capability not registered.
Fix: Add capability to role:
$hooks->wpAction('init', function () use ($context) {
$role = get_role('administrator');
$role->add_cap($context->prefix() . 'edit');
});
Sanitizer Issues
HTML stripped unexpectedly
Problem: $sanitizer->text($content) removes allowed HTML.
Cause: Wrong sanitizer method for content type.
Fix: Use correct method:
// Plain text (strips all HTML)
$title = $sanitizer->text($input);
// HTML content (allows safe tags)
$content = $sanitizer->html($input);
// Textarea (preserves newlines)
$notes = $sanitizer->textarea($input);
Email sanitization returns empty
Problem: $sanitizer->email($input) returns empty string.
Cause: Invalid email format.
Fix: Validate before assuming valid:
$email = $sanitizer->email($input);
if (empty($email) && !empty($input)) {
// Invalid email provided
$errors[] = __('Invalid email format', $context->textDomain());
}
RateLimiter Issues
Rate limit not enforced
Problem: tooManyAttempts() never returns true.
Cause: Transient not persisting or wrong key.
Fix: Verify unique key per user/action:
// Bad: Same key for all users
$limiter->tooManyAttempts('login');
// Good: Unique per IP
$key = 'login_' . $_SERVER['REMOTE_ADDR'];
$limiter->tooManyAttempts($key);
Rate limit persists after clearing
Problem: User still blocked after transient should expire.
Cause: Object cache caching transients.
Fix: Clear object cache:
wp_cache_delete($context->transientKey($key), 'transient');