Install
$ agentstack add skill-wpultimatesecurity-wordpress-security-skills-secrets-credentials-management ✓ 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
Secrets & credentials management
When to use this skill
Use this skill whenever code handles sensitive credentials:
- Storing an API key, webhook secret, or service token from a settings form.
- Building a custom login or token-verification flow.
- Generating reset tokens, API keys, or one-time nonces that must stay secret.
- Logging or exporting data that might accidentally include secrets.
- Deciding whether to use the user's account password or an application-specific
password for API/integration access.
Secrets in source code, reversible password storage, and plaintext API keys in options are common sources of credential leaks.
Related: see the nonces-csrf-protection skill for CSRF tokens and the settings-options-security skill for sanitizing option values.
Core principles (and why they matter)
- Never hardcode secrets. API keys and passwords committed to source control leak to
anyone with repo access and often end up in public repositories.
- Never store user passwords reversibly. WordPress stores passwords as salted hashes
via wp_hash_password(); use wp_check_password() to verify. Do not roll your own hash.
- Prefer wp-config constants or encrypted option storage for service keys. Put keys in
wp-config.php constants or encrypt them at rest with libsodium so the database alone is not enough to recover them.
- Use Application Passwords for API auth. WordPress 5.6+ provides
WP_Application_Passwords
for machine-to-machine auth without exposing the user's account password.
- Never echo or log secrets. Tokens and keys should never appear in HTML, logs, error
messages, shell history, or exported backups.
- Generate tokens with
wp_generate_password()orwp_create_nonce(). They are designed
for cryptographic strength (nonces are short-lived; passwords/tokens can be longer).
Step-by-step implementation
- For user passwords: hash with
wp_hash_password()on save, verify withwp_check_password(). - For service/API keys:
- Accept via a secure settings form (nonce + capability + sanitize).
- Store in a
wp-config.phpconstant if possible, or encrypt withsodium_crypto_secretbox()
using a key derived from wp_salt() (or a dedicated constant) before update_option().
- For machine auth: use
WP_Application_Passwords::create_new_application_password(). - For tokens: generate with
wp_generate_password( $length, false ); store only a hash if you
need to verify it later.
- Sanitize any log/export output to redact known secret keys.
- If a secret leaks, respond in this order:
- Rotate first. Assume compromise the moment the secret left your control — once
pushed to a remote, treat it as captured (clones, forks, and scrapers may already hold it). Issue a new key at the provider, then update the wp-config constant or re-save the encrypted option, and revoke the old key; issuing a replacement alone may leave the leaked key usable. For active abuse, revoke immediately.
- Revoke what rotation cannot cover. Delete leaked Application Passwords
(Users → Profile → Application Passwords) and destroy sessions for affected users with WP_Session_Tokens::get_instance( $affected_user_id )->destroy_all() for each trusted, verified affected user ID. wp_destroy_all_sessions() targets only the current user, not an arbitrary affected user. Session revocation does not revoke Application Passwords. If wp-config.php leaked, rotate all eight keys/salts and exposed database/service credentials. If an encryption key changes, re-encrypt retained secrets before discarding it; salt-derived keys also change when salts rotate. See wp-hardening-best-practices.
- Consider history cleanup after revocation, never instead. If needed, plan a
git filter-repo rewrite with repository owners and collaborators. Do not automatically rewrite history or force-push: publishing rewritten history is destructive and requires explicit approval and coordinated protection of others' work. Old clones still contain the secret and can reintroduce it; arrange re-cloning or careful cleanup. Forks and cached views need separate coordination; consult GitHub Support's removal criteria where applicable.
- Add push protection to block supported secrets when pushing to GitHub, not
when making local commits. Use local secret scanning/pre-commit checks for earlier feedback; neither catches every secret or replaces safe handling.
Common AI mistakes / anti-patterns
Mistake 1 — API key as a string literal
// ❌ Insecure: key is in source control and readable by anyone with file access.
$api_key = 'sk-live-abc123';
// ✅ Secure: read from a wp-config constant or an encrypted option.
if ( ! defined( 'MY_PLUGIN_API_KEY' ) ) {
wp_die( esc_html__( 'API key is not configured.', 'my-plugin' ) );
}
$api_key = MY_PLUGIN_API_KEY;
Mistake 2 — Storing user passwords in plaintext or with a weak hash
// ❌ Insecure: reversible or weak storage.
update_option( 'my_plugin_user_password', $_POST['password'] );
update_option( 'my_plugin_user_password', md5( $_POST['password'] ) );
// ✅ Secure: use WordPress password hashing.
$hash = wp_hash_password( $_POST['password'] );
update_option( 'my_plugin_user_password_hash', $hash );
// Verify later:
if ( wp_check_password( $submitted, $stored_hash ) ) { /* ... */ }
Mistake 3 — Logging tokens or API keys
// ❌ Insecure: secret lands in error_log / shell history.
error_log( 'Request failed with token: ' . $api_key );
// ✅ Secure: log an identifier or redacted value.
error_log( 'Request failed for token ending in: ' . substr( $api_key, -4 ) );
Mistake 4 — Reusing nonces as long-lived secrets
// ❌ Insecure: nonces expire and are not designed for persistent auth.
$token = wp_create_nonce( 'my_api' );
update_user_meta( $user_id, 'my_api_token', $token );
// ✅ Secure: generate a dedicated token and hash it for storage.
$token = wp_generate_password( 32, false );
$token_hash = wp_hash_password( $token );
update_user_meta( $user_id, 'my_api_token_hash', $token_hash );
// Hand the plaintext token to the user once, then verify with wp_check_password().
Mistake 5 — Storing service keys in plaintext options
// ❌ Insecure: database dump exposes the key.
update_option( 'my_plugin_stripe_key', $_POST['stripe_key'] );
// ✅ Secure: encrypt at rest with libsodium (PHP 7.2+).
function my_plugin_encrypt_secret( $plaintext ) {
$key = sodium_base642bin( MY_PLUGIN_ENCRYPTION_KEY, SODIUM_BASE64_VARIANT_ORIGINAL );
$nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$cipher = sodium_crypto_secretbox( $plaintext, $nonce, $key );
return base64_encode( $nonce . $cipher );
}
function my_plugin_decrypt_secret( $encoded ) {
$raw = base64_decode( $encoded );
$nonce = substr( $raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$cipher = substr( $raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$key = sodium_base642bin( MY_PLUGIN_ENCRYPTION_KEY, SODIUM_BASE64_VARIANT_ORIGINAL );
return sodium_crypto_secretbox_open( $cipher, $nonce, $key );
}
Correct code examples
A complete settings-page snippet that stores an API key either as a wp-config constant or encrypted in options is in [references/secure-secret-storage.php](references/secure-secret-storage.php).
Checklist
- [ ] No secrets, API keys, or passwords are hard-coded in source files.
- [ ] User passwords are hashed with
wp_hash_password()and verified withwp_check_password(). - [ ] Service/API keys are stored in
wp-config.phpconstants or encrypted at rest. - [ ] Machine-to-machine auth uses Application Passwords where possible.
- [ ] Tokens are generated with
wp_generate_password()(orwp_create_nonce()for short-lived CSRF). - [ ] Stored tokens are verified against a hash, not compared in plaintext.
- [ ] Secrets are never echoed, logged, exported, or sent to the browser.
- [ ] Leaked credentials are revoked/rotated; affected users' sessions are revoked when warranted.
- [ ] Any history rewrite/publication has explicit approval and coordinated clone/fork cleanup.
- [ ] Settings forms that collect secrets use nonce + capability + HTTPS.
Official references
wp_destroy_all_sessions()— current user onlyWP_Session_Tokens::get_instance()- GitHub — removing sensitive data
- GitHub — push protection
wp_hash_password()wp_check_password()wp_generate_password()wp_salt()WP_Application_Passwordswp_authenticate_application_password()sodium_crypto_secretbox()- Application Passwords — WordPress.org
- OWASP — Secrets Management 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.