T09 · Insecure Skill Coding Practices
- Location
scripts/wp_publisher.py:835- Finding
WordPress application password exposed through command-line arguments
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a real WordPress publisher, but it handles powerful site credentials and can publish or modify live content with insufficient safety boundaries.
Review carefully before installing on a production WordPress site. Use a dedicated least-privileged WordPress account, avoid passing application passwords on the command line, publish drafts first, confirm every live publish/update/delete action, and do not publish untrusted HTML or links without sanitization.
scripts/wp_publisher.py:835WordPress application password exposed through command-line arguments
scripts/content_to_gutenberg.py:29Unsanitized HTML and URL attributes can produce active content in published WordPress posts
{p_match.group(1)}
\n' f'The code accurately supports only one subset of the description: generating Gutenberg-compatible block markup, including tables, images, lists, headings, quotes, code blocks, and some helper block creators. However, the declared purpose centers on direct WordPress publishing and related CMS features. There is no REST API usage, no HTTP/network access, no authentication, no category retrieval, no SEO tag generation, and no preview/publish workflow. Therefore the description materially overstates the implemented behavior.
There is a clear mismatch between the declared description and the provided code. The description claims substantial WordPress publishing and content-conversion functionality, but the actual code chunk is only a placeholder test package file (tests/__init__.py) with no implementation. No declared capabilities are evidenced in the supplied code, so the description does not accurately represent the behavior of this code chunk.
The code chunk focuses narrowly on converting content into Gutenberg-compatible block markup and validating that markup. That partially aligns with the declared feature of converting markdown/HTML to Gutenberg blocks, including tables, images, lists, and formatting. However, the declared primary purpose is broader and centered on direct WordPress publishing through the REST API, plus category management, SEO tag generation, and preview functionality. None of those capabilities appear in this code. Because the actual code only implements/testing conversion helpers rather than the advertised publishing workflow, the description materially overstates what this code chunk does.
Referenced artifact was not completely inspected
- `references/gutenberg-blocks.md` - Block format reference
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
Every Gutenberg block follows this pattern:
<!-- wp:block-name {"attribute":"value"} -->
<html-content>Content here</html-content>
<!-- /wp:block-name -->
This markdown file documents WordPress REST API operations including authentication and creating, updating, deleting, and publishing content, which can affect live site data. There is no accompanying warning in the file about modifying remote content, using credentials, or the risk of unintended publication/deletion.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
- **Gutenberg Block Support**: Full conversion of Markdown/HTML to WordPress Gutenberg blocks
- **Smart Category Selection**: Auto-load categories from your site and intelligently match content
- **SEO Tag Generation**: Automatically generate relevant tags for better discoverability
- **Preview Workflow**: Create drafts, preview, then publish with confidence
- **Media Management**: Upload and attach featured images
- **Scheduled Publishing**: Schedule posts for future publication
- **CLI Support**: Use from command line for automation
The README documents direct publishing, including status="publish", but does not prominently warn that these actions can immediately create or modify live content on a production WordPress site. In a skill designed to operate against remote CMS instances, missing safety guidance increases the chance of accidental publication, unauthorized production changes by confused users, or misuse in the wrong environment.
The CLI example shows passing site URL, username, and application password directly to a command that can publish content remotely, but it omits a clear warning about sending credentials to WordPress and potentially creating or modifying posts. This is dangerous because users may run the command against production without realizing its side effects or may expose secrets through shell history and process listings.
The skill demonstrates network access to a live WordPress site and references local script modules, but it does not declare any explicit tool scope such as allowed network domains, file access boundaries, or required permissions. That makes the operational boundary ambiguous and can enable overbroad execution, especially where credentials are handled and content can be published to external systems.
The invocation text includes broad triggers such as 'publish to blog' and 'create WordPress article,' which can cause the skill to activate on common user requests without sufficiently narrow consent or destination scoping. In a skill that can authenticate and publish to external infrastructure, overbroad invocation increases the risk of unintended posting, credential requests in the wrong context, or misuse of a connected site.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
upload_headers = {"Authorization": self.headers["Authorization"]}
response = requests.post(url, headers=upload_headers, files=files, data=data, timeout=60)
else:
response = requests.post(url, headers=self.headers, json=data, timeout=30)
elif method == 'PUT':
response = requests.put(url, headers=self.headers, json=data, timeout=30)
elif method == 'DELETE':
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
else:
response = requests.post(url, headers=self.headers, json=data, timeout=30)
elif method == 'PUT':
response = requests.put(url, headers=self.headers, json=data, timeout=30)
elif method == 'DELETE':
response = requests.delete(url, headers=self.headers, params=params, timeout=30)
else:
This code file supports a safety-critical operation: setting --status publish causes content to be posted live to a WordPress site. While success output is shown after the fact, there is no confirmation prompt or explicit warning near the publish-status option that choosing publish will make content publicly visible and modify the remote site immediately.
The changelog states that the skill can upload images and files and set featured images, which involves sending user-provided content to a remote service. The markdown does not warn users about external transmission of files or the potential privacy implications.
This markdown file documents use of the raw HTML block and provides an iframe example, which can affect privacy, load third-party content, and bypass safer structured blocks. The section does not include any warning about only embedding trusted sources or the implications of rendering arbitrary external content.
The markdown describes YouTube, Twitter/X, and generic embeds, all of which can load third-party resources and expose visitor metadata to external services. There is no warning here about privacy implications, consent considerations, or restricting embeds to trusted providers.
The development dependency uses a lower-bound version specifier (pytest>=7.0.0) instead of pinning to an exact version or constrained range. This weakens build reproducibility and can allow installation of unexpected or later vulnerable releases, especially relevant because the file also references advisory-bearing packages and pulls from another requirements file.
-r requirements.txt
# Testing
pytest>=7.0.0
pytest-cov>=4.0.0
pytest-mock>=3.10.0
responses>=0.22.0
The manifest does not pin pytest, so it is impossible to verify whether the installed version includes or avoids the cited advisory. This uncertainty is itself a security weakness because CI or local installs may resolve to affected versions depending on when and where dependencies are installed.
pytest-cov is unpinned and may resolve to different versions over time, making development environments non-reproducible. While this is a dev dependency, compromised or breaking upstream releases can still affect CI, test execution, and supply-chain trust.
# Testing
pytest>=7.0.0
pytest-cov>=4.0.0
pytest-mock>=3.10.0
responses>=0.22.0
pytest-mock is specified with only a minimum version, so future installs may pull in unreviewed releases. This creates a supply-chain and reproducibility risk even if the package is only used in testing.
# Testing
pytest>=7.0.0
pytest-cov>=4.0.0
pytest-mock>=3.10.0
responses>=0.22.0
# Code quality
responses is unpinned, allowing dependency resolution to vary across environments and over time. In CI and development tooling, this can introduce unanticipated vulnerable or malicious package versions if the upstream supply chain is compromised.
pytest>=7.0.0
pytest-cov>=4.0.0
pytest-mock>=3.10.0
responses>=0.22.0
# Code quality
black>=23.0.0
black is unpinned, and the file separately notes that black has multiple known advisories across versions. Without an exact version, installs may land on a vulnerable release or change behavior unexpectedly in developer and CI environments.
responses>=0.22.0
# Code quality
black>=23.0.0
flake8>=6.0.0
isort>=5.12.0
mypy>=1.0.0
black has known advisories across some versions, but the open-ended requirement prevents verification that only safe versions will be installed. Because formatting tools run automatically in developer and CI workflows, a vulnerable version could be executed in trusted environments.
flake8 is not pinned, which reduces reproducibility and increases supply-chain uncertainty. Although it is a code-quality tool rather than runtime code, dev-tool compromise can still affect source integrity, CI outcomes, or developer workstations.
# Code quality
black>=23.0.0
flake8>=6.0.0
isort>=5.12.0
mypy>=1.0.0
No suspicious patterns detected.