Security
Overview
WPZylos Scheduler executes user-defined code on a recurring basis. This has inherent security considerations that must be addressed in your implementation.
Task Callback Security
Never Trust User Input in Tasks
Scheduled tasks may interact with the database, external APIs, or the filesystem. Always sanitize and validate:
// Good: Good — sanitized query
$schedule->call(function () {
global $wpdb;
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}tokens WHERE user_id = %d AND expires_at < %s",
$user_id,
current_time('mysql')
)
);
})->daily()->name('clean-tokens');
// Bad: Bad — unsanitized input
$schedule->call(function () {
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->prefix}tokens WHERE status = '{$_GET['status']}'");
})->daily()->name('unsafe-cleanup');
Escape Output
If tasks generate output (emails, logs), escape content:
$schedule->call(function () {
$data = get_option('report_data');
wp_mail(
get_option('admin_email'),
esc_html('Daily Report'),
wp_kses_post($data)
);
})->dailyAt('08:00')->name('daily-report');
Lock Security
Transient-Based Locks
Locks are stored as WordPress transients. Be aware:
- Database-backed: In default WordPress, transients may be stored in the
wp_optionstable - Object cache: If an object cache (Redis, Memcached) is configured, transients use it instead
- Expiration: Always set appropriate lock expiration times to prevent deadlocks
// Set reasonable lock durations
$schedule->call($longTask)
->withoutOverlapping(120) // 2 hours max
->name('import-task');
Lock Key Collision
Task names are hashed with MD5 for lock keys. While collision probability is extremely low, always use unique, descriptive task names:
// Good: Good — unique names
$schedule->call($fn)->name('myplugin-clean-tokens');
$schedule->call($fn)->name('myplugin-sync-users');
// Bad: Bad — generic names that might collide across plugins
$schedule->call($fn)->name('cleanup');
$schedule->call($fn)->name('sync');
WP-CLI Command Security
When scheduling WP-CLI commands, never include user-controllable input:
// Good: Good — static command
$schedule->command('cache flush')->hourly()->name('cache-flush');
// Bad: Bad — dynamic command from option (could be manipulated)
$command = get_option('custom_command');
$schedule->command($command)->hourly()->name('custom-cmd');
Job Class Security
When scheduling job classes, only use trusted, autoloaded classes:
// Good: Good — known class
$schedule->job(ProcessOrders::class)->everyFiveMinutes()->name('process-orders');
// Bad: Bad — class name from user input
$schedule->job($_POST['job_class'])->everyFiveMinutes()->name('dynamic-job');
Capability Checks
If tasks perform privileged operations, verify capabilities even in cron context:
$schedule->call(function () {
// Although running in cron context, verify this is a legitimate execution
if (!wp_doing_cron() && !current_user_can('manage_options')) {
return;
}
// Privileged operation
delete_option('sensitive_data');
})->daily()->name('admin-cleanup');
Error Information Disclosure
The wpzylos_scheduler_task_failed action receives the full exception. Don't expose internal details to end users:
add_action('wpzylos_scheduler_task_failed', function ($task, $e) {
// Good: Good — log internally
error_log("[Scheduler] {$task->getName()} failed: {$e->getMessage()}");
// Bad: Bad — don't expose to frontend
// echo $e->getTraceAsString();
}, 10, 2);
Reporting Vulnerabilities
If you discover a security vulnerability in WPZylos Scheduler, please report it responsibly. See security.md for instructions.