Back to skill

Security audit

Wordpress OAuth

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it stores and handles WordPress OAuth secrets in ways that could leak them, so users should review it carefully before installing.

Install only if you are comfortable with this skill storing a reusable WordPress bearer token in its own directory and using it to create or publish posts. Prefer the smallest OAuth scope and draft status, keep the skill directory private, avoid running the secret-bearing commands in logged shells or CI, and revoke the WordPress token if credentials.json or command logs may have been exposed.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
wp_oauth_skill.py:148
Finding
OAuth Secrets Exposed Through Process Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `wp_oauth_skill.py:148-151`, `wp_oauth_skill.py:281-286`; documented usage in `SKILL.md:32-38` **Vulnerability Type**: Sensitive information exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python def exchange_token(args: argparse.Namespace) -> dict[str, Any]: client_id = get_required(args.client_id, "WPCOM_CLIENT_ID", "--client-id") client_secret = get_required( args.client_secret, "WPCOM_CLIENT_SECRET", "--client-secret" ) redirect_uri = get_required( args.redirect_uri, "WPCOM_REDIRECT_URI", "--redirect-uri" ) code = args.code callback_state = args.state if args.callback_url: parsed_code, parsed_state, oauth_error = parse_callback(args.callback_url) ``` ```python p_exchange.add_argument("--client-id", default=None) p_exchange.add_argument("--client-secret", default=None) p_exchange.add_argument("--redirect-uri", default=None) p_exchange.add_argument("--callback-url", default=None) p_exchange.add_argument("--code", default=None) p_exchange.add_argument("--state", default=None) ``` The documented command encourages supplying these values as command-line arguments: ```bash python3 {baseDir}/wp_oauth_skill.py exchange-token \ --client-id "$WPCOM_CLIENT_ID" \ --client-secret "$WPCOM_CLIENT_SECRET" \ --redirect-uri "$WPCOM_REDIRECT_URI" \ --callback-url "https://example/callback?code=...&state=..." ``` ### Technical Analysis The OAuth client secret, authorization code, and complete callback URL are accepted through command-line flags. Shell variable expansion occurs before process creation, so the expanded values can appear in the Python process argument vector. Depending on the operating system and execution environment, command-line arguments may be exposed through process-monitoring interfaces, diagnostic tooling, shell tracing, terminal or session recording, job-runner logs, and command-history workflows. ...[truncated 2128 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement the advertised environment-variable fallback rather than merely mentioning it in the error message: ```python import os def get_required(value: str | None, env_name: str, flag_name: str) -> str: resolved = value or os.environ.get(env_name) if resolved: return resolved raise SkillError( f"Missing required value. Provide {flag_name} or set {env_name}." ) ``` 2. Prefer reading the client secret from a protected environment variable, standard input, a secret manager, or `getpass.getpass()` instead of a command-line option. 3. Allow the callback URL or authorization code to be supplied through standard input or a protected file descriptor. 4. Update `SKILL.md` so the recommended command does not include secret-bearing flags. 5. Retain command-line secret options only if compatibility requires them, and display an explicit warning that process arguments may be logged or visible locally. 6. Ensure CI systems and process supervisors do not log expanded commands or environment variables containing OAuth material. 7. Clear temporary references to authorization codes and avoid including them in errors or debug output. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
wp_oauth_skill.py:212
Finding
Bearer Access Token Included in Token-Info URL Query String<![CDATA[ ## Vulnerability Details **File Location**: `wp_oauth_skill.py:212-213` **Vulnerability Type**: Bearer-token exposure through URL logging **Risk Level**: Medium ### Vulnerable Code ```python client_id = args.client_id or credentials.get("client_id") if not client_id: raise SkillError("Client ID is required. Provide --client-id.") query = urlencode({"client_id": client_id, "token": access_token}) info = request_json("GET", f"{TOKEN_INFO_ENDPOINT}?{query}") return { "access_token_masked": mask_token(access_token), "token_info": info, } ``` ### Technical Analysis The `token-info` command constructs a GET request with the full bearer access token in the URL query string: ```text https://public-api.wordpress.com/oauth2/token-info?client_id=...&token=... ``` The request uses HTTPS and targets a fixed official WordPress.com endpoint, so the token is encrypted in transit and this behavior is not evidence of hidden exfiltration. Sending the token to WordPress is necessary to validate it. The insecure aspect is placing the credential in the URL. URLs are more likely than request headers or bodies to be retained by HTTP servers, reverse proxies, gateways, monitoring agents, tracing systems, debugging tools, and exception reports. The request helper also embeds the complete URL in HTTP error messages: ```python raise SkillError(f"HTTP {exc.code} for {url}: {detail}") from exc ``` If the token-info endpoint returns an HTTP error, the generated error can therefore contain the complete bearer token. Although the CLI writes that message to standard error rather than intentionally transmitting it elsewhere, surrounding automation may persist it in logs. ### Attack Path 1. A valid access token is stored in `credentials.json`. 2. A user or automated process invokes the `token-info` command. 3. The Skill inserts the complete token into the request URL. 4. An HTTP server, proxy, monitoring component, diagnostic collector, or failed-request log reco ...[truncated 1121 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. If supported by the WordPress token-info API, transmit the token in an authorization header: ```python info = request_json( "GET", TOKEN_INFO_ENDPOINT, headers={"Authorization": f"Bearer {access_token}"}, ) ``` 2. If the endpoint requires form parameters, prefer an HTTPS POST request with the token in the request body rather than in the URL. 3. If WordPress requires the token in the query string, treat this as residual endpoint-mandated risk and ensure URLs are redacted before logging. 4. Modify `request_json()` so error messages do not include query strings. For example, retain only the scheme, host, and path: ```python parsed = urlparse(url) safe_url = parsed._replace(query="", fragment="").geturl() raise SkillError(f"HTTP {exc.code} for {safe_url}: {detail}") from exc ``` 5. Sanitize response details as well, because upstream services may echo submitted parameters in error bodies. 6. Configure proxies, application monitoring, tracing systems, and CI logs to redact `token` query parameters. 7. Revoke and replace any token suspected of appearing in historical logs. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (8)

Credential Access

High
Category
Privilege Escalation
Content
---
name: wordpress-oauth
description: Start and complete WordPress.com OAuth and publish posts through the WordPress.com REST API. Use when you need to generate an authorization URL, exchange callback code for an access token, validate token health, or publish draft/published posts to a WordPress.com or Jetpack-connected site.
---

# WordPress OAuth Skill
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
---
name: wordpress-oauth
description: Start and complete WordPress.com OAuth and publish posts through the WordPress.com REST API. Use when you need to generate an authorization URL, exchange callback code for an access token, validate token health, or publish draft/published posts to a WordPress.com or Jetpack-connected site.
---

# WordPress OAuth Skill
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
- Script: `{baseDir}/wp_oauth_skill.py`
- OAuth state store: `{baseDir}/oauth_state.json`
- Credential store: `{baseDir}/credentials.json`

This skill stores state and credentials in files inside this skill directory.
Confidence
89% confidence
Finding
The skill explicitly documents a local credential store inside the skill directory, which means bearer tokens and related secrets are persisted on disk in a likely predictable location. Stored OAuth credentials are highly sensitive; if the directory is readable by other agents, users, backups, or logs, an attacker could reuse the token to access or publish to the connected WordPress site.

Credential Access

High
Category
Privilege Escalation
Content
--callback-url "https://example/callback?code=...&state=..."
```

Validates CSRF `state`, exchanges code for token, and writes credentials to `{baseDir}/credentials.json`.

### 3) Token Info
Confidence
93% confidence
Finding
This step states that exchanged OAuth credentials are written to credentials.json, confirming persistent on-disk storage of access material after the callback flow. Persistent bearer tokens materially increase compromise impact because anyone obtaining the file can authenticate to the WordPress API without needing the user's password or another approval step.

Credential Access

High
Category
Privilege Escalation
Content
BASE_DIR = Path(__file__).resolve().parent
STATE_PATH = BASE_DIR / "oauth_state.json"
CREDS_PATH = BASE_DIR / "credentials.json"


class SkillError(Exception):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
BASE_DIR = Path(__file__).resolve().parent
STATE_PATH = BASE_DIR / "oauth_state.json"
CREDS_PATH = BASE_DIR / "credentials.json"


class SkillError(Exception):
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documentation exposes capabilities requiring file read/write, network access, and shell execution, but it does not declare any explicit tool scope or permissions boundary. That omission increases risk because consumers or orchestration layers may permit broader access than necessary, making credential handling and remote publishing operations less constrained.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill persists the OAuth access token in a local JSON file on disk, which creates a recoverable secret at rest in the workspace. Although the file permissions are restricted with chmod(0o600), that protection is best-effort only and may not be enforced on all platforms or against other processes/users with access to the workspace, so compromise of the directory exposes the token for API use.

Static analysis

No suspicious patterns detected.