Back to skill

Security audit

WordPress REST API

Security checks for vulnerabilities and agentic risk

Overview

This WordPress REST helper is mostly purpose-aligned, but its credential-handling script can expose application passwords if used carelessly or with an unsafe URL.

Review before installing. Use this only with test or narrowly scoped WordPress application passwords, prefer HTTPS-only targets, avoid putting real application passwords directly in shell history, and do not use authenticated calls with absolute --route URLs unless the script is fixed to enforce same-origin HTTPS behavior.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/inspect-rest-api.sh:43
Finding
Application Password Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/inspect-rest-api.sh`, lines 43–44 and 100–101 **Vulnerability Type**: Credential exposure through process arguments and shell history **Risk Level**: High ### Vulnerable Code ```bash --app-password) APP_PASSWORD="${2:-}" shift 2 ;; ``` The captured secret is subsequently placed in the `curl` argument vector: ```bash if [[ -n "$USER_NAME" ]]; then CURL_ARGS+=(--user "${USER_NAME}:${APP_PASSWORD}") fi ``` The documented usage also instructs users to provide the secret directly on the command line: ```bash inspect-rest-api.sh --site https://example.com --user admin --app-password "xxxx xxxx xxxx xxxx" ``` ### Technical Analysis The WordPress application password is accepted as a command-line parameter and then interpolated into the `curl --user` argument. This creates two separate exposure points: 1. The initial script command can be recorded in interactive shell history. 2. While the script and `curl` are running, the secret may be observable through process-inspection interfaces or system monitoring tools capable of reading command-line arguments. An application password is an authentication credential. Although it can be individually revoked and may have less operational scope than the primary account password, it authorizes REST API actions available to the associated WordPress user. Passing it through an argument vector is not necessary for the Skill’s route-inspection functionality. ### Attack Path 1. A user follows the documented authenticated usage and invokes the script with `--app-password`. 2. The command, including the plaintext application password, may be retained in shell history. 3. The script constructs a child-process argument containing `username:application-password`. 4. A local user, process monitor, audit collector, or compromised process with sufficient access reads the command history or process arguments. 5. The attacker submits requests to the target WordPre ...[truncated 721 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `--app-password PASS` command-line interface. - Read the password interactively from a terminal without echoing it, for example with `read -r -s`, when interactive use is intended. - For automation, accept credentials through a protected file or file descriptor with restrictive permissions rather than an argument. - Do not merely replace command-line arguments with a broadly exposed environment variable, because process environments may also be readable under some operating conditions. - Avoid passing the secret to `curl` as a visible argument. Use a temporary `curl` configuration or netrc file created with mode `0600`, pass only its filename to `curl`, and remove it immediately after use. - Update `SKILL.md`, the script usage output, and all examples so they no longer encourage placing application passwords in shell commands. - Recommend creating a dedicated WordPress automation account with only the capabilities required by the intended operation, and use a separately revocable application password for that account. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/inspect-rest-api.sh:74
Finding
Basic Authentication Credentials Can Be Transmitted Over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/inspect-rest-api.sh`, lines 74–104 **Vulnerability Type**: Plaintext transmission of sensitive authentication data **Risk Level**: High ### Vulnerable Code ```bash if [[ -n "$ROUTE" ]]; then if [[ "$ROUTE" == http://* || "$ROUTE" == https://* ]]; then TARGET_URL="$ROUTE" else ROUTE="/${ROUTE#/}" if [[ "$ROUTE" == /wp-json/* ]]; then TARGET_URL="${SITE}${ROUTE}" else TARGET_URL="${SITE}/wp-json${ROUTE}" fi fi else TARGET_URL="${SITE}/wp-json/" fi TMP_BODY="$(mktemp)" trap 'rm -f "$TMP_BODY"' EXIT CURL_ARGS=( --silent --show-error --location --request "$METHOD" --header "Accept: application/json" --output "$TMP_BODY" --write-out "%{http_code}" ) if [[ -n "$USER_NAME" ]]; then CURL_ARGS+=(--user "${USER_NAME}:${APP_PASSWORD}") fi STATUS_CODE="$(curl "${CURL_ARGS[@]}" "$TARGET_URL")" ``` ### Technical Analysis The script explicitly accepts both `http://` and `https://` absolute route URLs and does not validate the scheme of `--site`. When credentials are provided, it unconditionally configures `curl` Basic Authentication regardless of the target scheme. HTTP Basic Authentication encodes the username and password but does not encrypt them. When the request is sent over HTTP, a network-positioned attacker can recover the Authorization header. The reference documentation recommends HTTPS, but advisory documentation does not enforce the security property at runtime. The use of authenticated HTTP is unnecessary for the declared WordPress REST inspection functionality because WordPress application passwords are intended to be used over HTTPS for remote automation. ### Attack Path 1. A user supplies an HTTP site or absolute route, either accidentally or after following an attacker-controlled target value. 2. The user also supplies a WordPress username and application password. 3. The script accepts the HTTP URL without warning or rejection. 4. ...[truncated 833 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse and validate the final target URL before invoking `curl`. - Reject authenticated requests unless the final target uses the `https` scheme. - Make HTTPS mandatory by default for all non-local targets. - If HTTP support is needed for local development, require an explicit option such as `--allow-insecure-http`, restrict it to loopback addresses by default, and prohibit credentials unless a second explicit acknowledgment is supplied. - Emit a clear error rather than silently sending credentials when the target is not HTTPS. - Consider using `curl --proto '=https'` for authenticated remote requests so unsupported or downgraded schemes are rejected. - Validate redirect behavior and protocols. Restrict redirects to HTTPS and ensure credentials cannot be forwarded to a different origin. - Continue recommending narrowly scoped, separately revocable WordPress application passwords even after transport validation is implemented. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/inspect-rest-api.sh:74
Finding
Absolute Route URL Can Redirect Trusted-Site Credentials to an Arbitrary Origin<![CDATA[ ## Vulnerability Details **File Location**: `scripts/inspect-rest-api.sh`, lines 74–76 and 100–104 **Vulnerability Type**: Credential disclosure caused by missing origin validation **Risk Level**: High ### Vulnerable Code ```bash if [[ -n "$ROUTE" ]]; then if [[ "$ROUTE" == http://* || "$ROUTE" == https://* ]]; then TARGET_URL="$ROUTE" else ROUTE="/${ROUTE#/}" if [[ "$ROUTE" == /wp-json/* ]]; then TARGET_URL="${SITE}${ROUTE}" else TARGET_URL="${SITE}/wp-json${ROUTE}" fi fi else TARGET_URL="${SITE}/wp-json/" fi ``` Credentials are then attached to the selected target without verifying that it belongs to the `--site` origin: ```bash if [[ -n "$USER_NAME" ]]; then CURL_ARGS+=(--user "${USER_NAME}:${APP_PASSWORD}") fi STATUS_CODE="$(curl "${CURL_ARGS[@]}" "$TARGET_URL")" ``` ### Technical Analysis The `--route` option is expected to identify a route on the WordPress site supplied through `--site`. However, when `--route` starts with `http://` or `https://`, it completely replaces the site-derived target. The script does not compare the absolute route’s scheme, hostname, or effective port with those of `--site`. Credentials supplied for a trusted WordPress site are therefore attached to a request sent directly to any origin selected through `--route`. This is a confused-deputy credential disclosure condition. Supporting arbitrary absolute route URLs is not required for the Skill’s stated route-discovery function; relative REST paths are sufficient. ### Attack Path 1. The user has a valid username and application password for `https://trusted.example`. 2. An attacker persuades the user or an automated agent to inspect an absolute route such as `https://attacker.example/collect`. 3. The command supplies `--site https://trusted.example`, the attacker-controlled absolute `--route`, and the trusted-site credentials. 4. The script assigns the attacker URL directly to `TARGET_URL`. 5. The script invokes `curl -- ...[truncated 751 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `--route` to be a relative REST path and reject values containing a URL scheme or authority component. - Construct the destination exclusively from the validated `--site` origin and a normalized relative path. - If absolute route URLs must remain supported, parse both URLs with a real URL parser and require exact equality of: - Scheme - Normalized hostname - Effective port - Never attach credentials after an origin change. - Reject URL user-information, malformed hosts, protocol-relative URLs, control characters, and ambiguous parsing forms. - Restrict authenticated requests to the expected `/wp-json/` path unless broader behavior is explicitly required. - Add tests proving that credentials are not sent when: - The route hostname differs from the site hostname. - The route scheme differs. - The effective port differs. - An absolute route is supplied. - A redirect attempts to cross origins. - Prefer removing absolute-route support entirely because it does not provide a necessary capability for normal WordPress route discovery. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The skill advertises broad WordPress REST functionality including authentication flows and read/write operations, but the described behavior is primarily route discovery and generic inspection. This mismatch can mislead users or upstream agents into assuming safe support for authenticated or write operations that are not actually implemented or constrained, increasing the chance of insecure workarounds, accidental misuse, or overtrust in the skill's capabilities.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill invokes shell-based scripts and relies on file reads, but it does not declare an explicit tool scope such as allowed tools or permissions. That omission weakens least-privilege boundaries and can let the runtime grant broader capabilities than operators expect, especially in environments that use metadata to enforce tool restrictions.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
This shell script performs an authenticated network request by passing `--user` credentials to `curl`, which sends the username and app password to the target URL. While the usage text shows the arguments, there is no explicit warning, confirmation, or user-facing disclosure that credentials will be transmitted over the network to the specified site.

Static analysis

No suspicious patterns detected.