Install
$ agentstack add skill-wpultimatesecurity-wordpress-security-skills-wp-cli-security ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
WP-CLI security
When to use this skill
Use this skill whenever code registers or implements a WP-CLI command:
WP_CLI::add_command( 'my-plugin thing', ... ).- Writing a command class method that reads
$args/$assoc_args. - Running destructive operations (delete, reset, import, purge) from the command line.
- Returning data or logging progress from a CLI routine.
WP-CLI runs as the system user, often as root on containers or with broad file-system permissions. It has no WordPress current user by default, so current_user_can() does not authorize CLI actions. Arguments are still untrusted input.
Related: see the capability-permission-checks skill for normal web-request authorization and the cron-background-job-security skill for scheduled jobs.
Core principles (and why they matter)
- No
current_user_can()in CLI by default. There is no logged-in WordPress user.
Either use WP_CLI::launch_self() with a --user flag, explicitly set a user with --user, or perform capability-independent maintenance tasks only.
- Sanitize every argument.
$argsand$assoc_argscan come from scripts, CI, or
shell history; treat them as untrusted input.
- Use prepared statements for database access. Do not interpolate
$argsinto
$wpdb queries.
- Confirm destructive actions.
WP_CLI::confirm()stops accidental data loss in
automated runs.
- Never print secrets. API keys, tokens, and passwords must not appear in CLI output,
logs, or shell history.
- Be explicit about
--allow-root. Running as root is common in containers but
dangerous; document it and avoid file ownership surprises.
Step-by-step implementation
- Register the command with
WP_CLI::add_command()and a clear docblock. - In the command method, validate argument counts and sanitize each value to type.
- For destructive commands, call
WP_CLI::confirm()unless--yesis passed. - Use
$wpdb->prepare()for any dynamic query. - Avoid echo; use
WP_CLI::log(),WP_CLI::success(), orWP_CLI::error(). - If the command needs a user context, require
--user=and load it with
WP_User::get_data_by() / wp_set_current_user().
Common AI mistakes / anti-patterns
Mistake 1 — Interpolating $args into a query
// ❌ Insecure: SQL injection via CLI argument.
$wpdb->query( "DELETE FROM {$wpdb->prefix}my_table WHERE id = {$args[0]}" );
// ✅ Secure: use $wpdb->prepare().
$id = absint( $args[0] );
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}my_table WHERE id = %d", $id ) );
Mistake 2 — Assuming an admin user context
// ❌ Insecure: current_user_can is meaningless without --user.
if ( current_user_can( 'manage_options' ) ) {
update_option( 'my_plugin_key', $value );
}
// ✅ Secure: require --user or gate by filesystem/CLI policy, not web capability.
class My_Plugin_CLI_Command {
/**
* Update a setting.
*
* ## OPTIONS
* [--user=]
* : User to run as.
*/
public function update_setting( $args, $assoc_args ) {
if ( isset( $assoc_args['user'] ) ) {
$user = get_user_by( 'login', $assoc_args['user'] );
if ( $user ) {
wp_set_current_user( $user->ID );
}
}
if ( ! current_user_can( 'manage_options' ) ) {
WP_CLI::error( 'This command requires --user with manage_options capability.' );
}
update_option( 'my_plugin_key', sanitize_text_field( $args[0] ) );
WP_CLI::success( 'Setting updated.' );
}
}
Mistake 3 — Printing secrets in output
// ❌ Insecure: secret is now in shell history and terminal scrollback.
WP_CLI::log( 'Using API key: ' . $api_key );
// ✅ Secure: log an identifier or redacted value.
WP_CLI::log( 'Using API key ending in: ' . substr( $api_key, -4 ) );
Mistake 4 — Destructive command with no confirmation
// ❌ Risky: a typo in CI wipes data.
public function purge( $args ) {
$wpdb->query( "TRUNCATE {$wpdb->prefix}my_plugin_logs" );
}
// ✅ Secure: require confirmation unless --yes is passed.
public function purge( $args, $assoc_args ) {
if ( ! isset( $assoc_args['yes'] ) ) {
WP_CLI::confirm( 'This will delete all logs. Are you sure?' );
}
global $wpdb;
$wpdb->query( "TRUNCATE {$wpdb->prefix}my_plugin_logs" );
WP_CLI::success( 'Logs purged.' );
}
Mistake 5 — Trusting associative args without defaults
// ❌ Fragile: missing key causes undefined array warning.
$days = $assoc_args['days'];
// ✅ Secure: provide defaults and sanitize.
$days = isset( $assoc_args['days'] ) ? absint( $assoc_args['days'] ) : 30;
Correct code examples
A complete secure WP-CLI command class is in [references/secure-wp-cli-command.php](references/secure-wp-cli-command.php).
Checklist
- [ ] Every CLI argument is sanitized to its expected type.
- [ ] Database queries use
$wpdb->prepare(); no argument interpolation. - [ ] Destructive commands call
WP_CLI::confirm()unless--yesis provided. - [ ] The command does not rely on
current_user_can()without an explicit--user. - [ ] Secrets, tokens, and API keys are never printed or logged in plaintext.
- [ ]
--allow-rootusage is documented and justified. - [ ] Output uses
WP_CLI::log()/success()/error()instead ofecho. - [ ] Command docblocks define options and examples.
Official references
- WP-CLI Commands — Handbook
WP_CLI::add_command()WP_CLI::error()WP_CLI::success()WP_CLI::log()WP_CLI::confirm()- Config — WP-CLI
- OWASP — Injection Prevention Cheat Sheet
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: wpultimatesecurity
- Source: wpultimatesecurity/WordPress-Security-Skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.