Back to skill

Security audit

WordPress API Pro

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed WordPress management toolkit with real site-mutation power, but its network, credential, file-read, and write behaviors fit its stated purpose and include reasonable user-control guidance.

Install only if you are comfortable giving this skill WordPress or WooCommerce API credentials with the power to change site content. Use a dedicated least-privileged WordPress user or WooCommerce key, keep any config/sites.json local and locked down, install dependencies in a virtual environment, review dry-run output before batch or seed writes, and avoid PageSpeed audits for private or confidential URLs because that sends the URL to Google.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (1)

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Mutable and Unhashed Third-Party Dependency Installation

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1 and SKILL.md:39-40
Vulnerability Type: Unlocked dependency resolution without package hashes
Risk Level: Medium

Complete Code Snippet:

From requirements.txt:1:

text
requests>=2.32.3,<3

Installation guidance from SKILL.md:39-40:

text
python3 -m pip install -r requirements.txt

The documentation explicitly states that the dependency uses a version range rather than an exact version or hash lock.

Technical Analysis

The installation process resolves requests and its transitive dependencies dynamically. The broad compatible range permits future releases below version 3 to be installed without those releases being represented in or reviewed as part of this Skill artifact. No lockfile or package hashes authenticate the exact distributions that pip downloads.

Dependency resolution may also be influenced by the user's pip configuration, including alternate or additional package indexes. Consequently, the code executed at installation time and imported at runtime can differ from the dependency set considered during this audit.

This is a supply-chain hardening weakness rather than evidence that the currently named requests package is malicious. The lower bound appropriately avoids known older vulnerabilities, but it does not provide reproducible or cryptographically verified installation.

Attack Path

  1. A user follows the documented command to install the requirements.
  2. Pip queries its configured package index or indexes and dynamically selects a compatible requests release and transitive dependencies.
  3. A compromised upstream release, compromised package index, or maliciously configured index supplies an attacker-controlled compatible distribution.
  4. Attacker-controlled build or installation logic executes with the privileges of the user running pip, or malicious runtime code is subsequently imported by t ...[truncated 892 chars]
Remediation
View remediation

Remediation Suggestions

  1. Generate and commit a reviewed dependency lockfile containing exact versions for requests and all transitive dependencies.
  2. Require hashes for every resolved distribution, for example by using a requirements file compatible with pip install --require-hashes.
  3. Install only from an explicitly trusted package index and prevent unintended fallback to additional indexes.
  4. Establish a controlled update process that regularly refreshes the lockfile, reviews dependency changes, runs security scans and tests, and promptly incorporates security patches.
  5. Prefer binary wheels from trusted sources where practical, and avoid executing unreviewed source-build logic.
  6. Install dependencies in an isolated virtual environment with no WordPress or WooCommerce credentials present during installation.
  7. Run the Skill under a dedicated, least-privileged local account and continue using narrowly scoped WordPress application passwords and WooCommerce keys.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (18)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-reference.md (reported line 17)May include surrounding context.

md
- `GET /posts/{id}` - Get single post
- `POST /posts` - Create post
- `POST /posts/{id}` - Update post
- `DELETE /posts/{id}` - Delete post

### Pages

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
85% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · scripts/security.py (reported line 465)May include surrounding context.

python
Basic-Auth credentials would travel unencrypted, and an app password read
    off the wire is the whole site. Localhost and .local/.test/.localhost dev
    hosts are exempt and never warn.

    Refusing is the default as of 3.9.0. The escape hatch is a HOST LIST, not a
    switch: WP_ALLOW_HTTP=staging.example.com (comma-separated for several)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/wp_cli.py (reported line 108)May include surrounding context.

python
cmd.extend(args)
    
    # Pass credentials via ENVIRONMENT VARIABLES (secure!)
    env = os.environ.copy()
    
    if site_config:
        env['WP_URL'] = site_config.get('url', env.get('WP_URL', ''))

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · SKILL.md (reported line 40)May include surrounding context.

md
- **Auth:** export `WP_URL` / `WP_USERNAME` / `WP_APP_PASSWORD`, or use `config/sites.json` for multi-site.
- **Dependencies:** the ACF / SEO / JetEngine / plugin-detection scripts need `requests` (`python3 -m pip install -r requirements.txt`, ideally in a venv). The pin is a RANGE, `requests>=2.32.3,<3`, not an exact version or a hash lock. That is deliberate: an exact pin stops users receiving patch-level security fixes for the dependency whose advisory is the reason for the lower bound, and this skill has no lockfile-refresh process to compensate. The trade is reproducibility for patchability.
   2.32.0 fixed CVE-2024-35195, where a `Session` that made one `verify=False` request silently skipped certificate verification for every later request to that host, and 2.32.3 closes out that line's follow-up regressions. The core post/page/media/WooCommerce/batch scripts use the stdlib only.
- **Local dev sites** (e.g. `http://site.local`, `localhost`, `*.test`, `*.localhost`) work over plaintext http. Any other host must be https:// unless you name it: `WP_ALLOW_HTTP=staging.example.com` (comma-separated for several). A blanket `WP_ALLOW_HTTP=1` is refused — it used to permit every host, so one variable set for one staging box covered production too.
- **Pairs with the Elementor MCP kit** (`siteagent-elementor-studio`): build page structure with the MCP, then do media uploads, SEO meta, custom fields, and WooCommerce here.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 49)May include surrounding context.

md
- **Never publish or update live content without explicit user approval.** Confirm target site, IDs, fields, and status.
- **Use least-privilege credentials.** Prefer a dedicated WordPress user/application password scoped to the required role.
- **Do not store production credentials in the repo.** Use environment variables when possible.
- **Protect config files.** If you create `config/sites.json`, keep it local, untracked, and `chmod 600 config/sites.json`.
- **Batch changes are dry-run by default.** Add `--execute` only after reviewing the dry-run output.
- **A created WooCommerce product is a draft.** WooCommerce publishes a product whose status is omitted; `woo_products.py --action create` sends `draft` unless you pass `--status publish`, and prompts at a TTY before a live one (`--yes` skips, non-interactive runs are never prompted).
- **Targeting every site is blocked by default.** Add `--allow-all` only when the user explicitly approved all configured sites.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 127)May include surrounding context.

bash
cp config/sites.example.json config/sites.json
chmod 600 config/sites.json

Use a dedicated user per site and keep app_password values local only.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/acf_fields.py (reported line 100)May include surrounding context.

python
# Try ACF REST endpoint first
    try:
        payload = {'fields': fields_dict}
        response = requests.post(f"{base_url}/wp-json/acf/v3/{rest_base}/{post_id}",
                               headers=headers, json=payload, timeout=10)
        if response.status_code in [200, 201]:
            return response.json()

Tainted flow: 'post_id' from requests.get (line 79, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/detect_plugins.py (reported line 61)May include surrounding context.

python
if posts and len(posts) > 0:
                post_id = posts[0]['id']
                # Get post meta
                post_response = requests.get(f"{base_url}/wp-json/wp/v2/posts/{post_id}", headers=headers, timeout=10)
                if post_response.status_code == 200:
                    post_data = post_response.json()
                    meta = post_data.get('meta', {})

Tainted flow: 'post_id' from requests.get (line 79, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/detect_plugins.py (reported line 80)May include surrounding context.

python
if posts and len(posts) > 0:
                post_id = posts[0]['id']
                # Get post meta
                post_response = requests.get(f"{base_url}/wp-json/wp/v2/posts/{post_id}", headers=headers, timeout=10)
                if post_response.status_code == 200:
                    post_data = post_response.json()
                    meta = post_data.get('meta', {})

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/acf_fields.py (reported line 110)May include surrounding context.

python
try:
        payload = {'meta': fields_dict}
        response = requests.post(f"{base_url}/wp-json/wp/v2/{rest_base}/{post_id}",
                               headers=headers, json=payload, timeout=10)
        if response.status_code in [200, 201]:
            return response.json()

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/jetengine_fields.py (reported line 90)May include surrounding context.

python
try:
        payload = {'meta': fields_dict}
        response = requests.post(f"{base_url}/wp-json/wp/v2/{rest_base}/{post_id}",
                               headers=headers, json=payload, timeout=10)
        if response.status_code in [200, 201]:
            return response.json()

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · scripts/security.py (reported line 359)May include surrounding context.

python
raise


def urlopen_probe(req, timeout=None):
    """urlopen for the unauthenticated site audit.

    Validates the requested URL and the target of every redirect against the

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · scripts/security.py (reported line 446)May include surrounding context.

python
raise


def urlopen_probe(req, timeout=None):
    """urlopen for the unauthenticated site audit.

    Validates the requested URL and the target of every redirect against the

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/seo_meta.py (reported line 201)May include surrounding context.

python
try:
        payload = {'meta': meta_payload}
        response = requests.post(f"{base_url}/wp-json/wp/v2/posts/{post_id}", 
                               headers=headers, json=payload, timeout=10)
        if response.status_code in [200, 201]:
            return response.json()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/wp_cli.py (reported line 123)May include surrounding context.

python
require_secure_wp_url(env['WP_URL'])

    # Run with modified environment
    result = subprocess.run(cmd, env=env)
    return result.returncode

def main():

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file documents a DELETE /posts/{id} operation, which can remove user content, but it provides no warning about the destructive nature or potential irreversibility of the action. Under the markdown-specific warning criterion, user-data-affecting behavior should be accompanied by a clear caution.

Content

No source excerpt is available for this finding.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified as a broad range (requests>=2.32.3,<3) rather than pinned to an exact vetted version, so builds are not fully reproducible and it is unclear which concrete release will be installed in different environments. Because requests has multiple historical advisories, leaving the version unpinned increases the chance of pulling a release with security issues or inconsistent patch status if dependency resolution changes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script sends the audited URL to Google's PageSpeed Insights API, which discloses the target to a third party. While this is likely intentional functionality rather than malicious behavior, it can expose confidential prospect, pre-engagement, staging, or otherwise sensitive URLs without an explicit user-facing warning or opt-in, creating a privacy and information disclosure risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.