Back to skill

Security audit

Daily Social Media Publisher

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed social media posting tool, but it can publish externally on a schedule and has unsafe network and credential handling that warrants review before installation.

Install only after reviewing the public-posting workflow and credentials. Use least-privilege API keys, protect the JSON config files, avoid passing secrets on the command line, restrict outbound network access to the intended provider domains, and require human approval or a dry run before scheduled posts go live.

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
index.py:73
Finding
Configurable API Endpoints Can Exfiltrate Bearer Credentials<![CDATA[ ## Vulnerability Details **File Location**: `index.py`, lines 73-108 **Vulnerability Type**: Unvalidated credential-bearing network destinations **Risk Level**: High ### Vulnerable Code ```python def render_image(self, text): """Generate image via Templated.io""" tpl_info = self.tpl_cfg.get('templates', {}).get(self.brand, {}) tpl_id = tpl_info.get('template_id') if not tpl_id: print("No template configured") return None headers = { "Authorization": f"Bearer {self.tpl_cfg.get('api_key')}", "Content-Type": "application/json" } data = { "template": tpl_id, "layers": {"text": {"text": text[:150]}}, "file_type": "png" } try: r = requests.post( self.tpl_cfg.get('endpoint', 'https://api.templated.io/v1/render'), headers=headers, json=data, timeout=60 ) ``` ```python def publish_post(self, image_data, caption): """Publish via UploadPost API""" headers = {"Authorization": f"Bearer {self.upload_cfg.get('api_key')}"} files = {"photos": ("post.png", image_data, "image/png")} data = {"caption": caption} try: r = requests.post( self.upload_cfg.get('endpoint', 'https://api.upload-post.com/api/upload_photos'), headers=headers, files=files, data=data, timeout=60 ) return r.json() ``` ### Technical Analysis The Templated.io and UploadPost destinations are obtained directly from externally supplied JSON configuration files. The implementation sends bearer credentials to these destinations without validating the URL scheme, hostname, port, resolved address, or redirect destination. Although communication with these APIs is necessary for the declared social-media publishing functionality, allowing credential-bearing requests to arbitrary configurable hosts exceeds minimum privilege. Anyone able to provide or modify either configuration file can replace an expected A ...[truncated 1573 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove endpoint overrides from untrusted runtime configuration and use fixed constants for the production API origins. 2. If custom endpoints are operationally required, enforce exact HTTPS allowlists, such as `api.templated.io` and `api.upload-post.com`. 3. Reject non-HTTPS schemes, embedded URL credentials, fragments, unexpected ports, malformed hostnames, and hosts outside the allowlist. 4. Disable redirects for credential-bearing requests with `allow_redirects=False`, or validate every redirect destination before following it. 5. Separate endpoint configuration from secret configuration and restrict both files with least-privilege filesystem permissions. 6. Use provider-scoped, minimally privileged API keys and rotate all credentials if an untrusted configuration may already have been used. 7. Store credentials in a secret manager or protected environment variables rather than in general-purpose JSON configuration. 8. Add tests confirming that attacker-controlled endpoints, alternate ports, subdomain tricks, and HTTP URLs are rejected before an authorization header is created or transmitted. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.py:84
Finding
Unvalidated Render URL Enables Server-Side Request Forgery and Response Relay<![CDATA[ ## Vulnerability Details **File Location**: `index.py`, lines 84-94 and 132-141 **Vulnerability Type**: Server-side request forgery through an untrusted render URL **Risk Level**: High ### Vulnerable Code ```python try: r = requests.post( self.tpl_cfg.get('endpoint', 'https://api.templated.io/v1/render'), headers=headers, json=data, timeout=60 ) if r.status_code == 200: url = r.json().get('render_url') if url: img = requests.get(url, timeout=30) if img.status_code == 200: return {"url": url, "data": img.content} except Exception as e: print(f"Image render error: {e}") return None ``` ```python # Generate image image = self.render_image(text) if not image: result["error"] = "Image generation failed" return result result["image_url"] = image.get("url") # Publish caption = f"{text}\n\n#{'PayLessTax' if self.brand == 'paylesstax' else 'LevelUpLove'}" post_result = self.publish_post(image.get("data"), caption) ``` ### Technical Analysis The application trusts the `render_url` value contained in the rendering service's JSON response and performs a server-side GET request to it. It does not validate: - The URL scheme or destination hostname - The resolved IP address - Loopback, private, link-local, reserved, or cloud metadata addresses - Redirect destinations - The response content type - The maximum response size - Whether the returned bytes are actually a valid image The downloaded bytes are retained in memory and then passed to `publish_post()`. Consequently, a malicious or compromised rendering endpoint can direct the process to retrieve an internal HTTP resource and relay the resulting bytes to the configured publication service. Control of the render response can arise through the separately identified configurable-endpoint weakness or through compromise of a trusted rendering service. Redirects also require validation because an initially acceptabl ...[truncated 1405 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit render downloads only from an explicit allowlist of trusted HTTPS rendering or CDN origins. 2. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. 3. Protect against DNS rebinding by ensuring that the validated address is the address actually used for the connection. 4. Disable redirects, or independently revalidate the scheme, hostname, port, and resolved address at every redirect hop. 5. Reject URLs containing embedded credentials and restrict destination ports to the required HTTPS port. 6. Stream the response instead of loading it unbounded into memory, enforcing a strict maximum byte count. 7. Require an approved image content type and decode the response with an image library to confirm that it is a valid image before upload. 8. Avoid returning or logging remote URLs containing sensitive query parameters. 9. Apply outbound network controls so the process cannot access cloud metadata, loopback services, or internal address ranges unless explicitly required. 10. Add automated tests for private-address URLs, IPv6 variants, redirect chains, DNS rebinding scenarios, invalid image data, and oversized responses. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.py:161
Finding
NewsAPI Credential Is Required as a Command-Line Argument<![CDATA[ ## Vulnerability Details **File Location**: `index.py`, lines 161-178 **Vulnerability Type**: Sensitive credential exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--brand', choices=['paylesstax', 'leveluplove'], required=True) parser.add_argument('--news-api-key', required=True) parser.add_argument('--uploadpost-config', required=True, help='Path to uploadpost.json') parser.add_argument('--templated-config', required=True, help='Path to templated_io.json') parser.add_argument('--oneliners', required=True, help='Path to oneliners JSON') parser.add_argument('--content-type', choices=['news', 'oneliner'], default='news') parser.add_argument('--output', default='post_result.json') args = parser.parse_args() # Load configs with open(args.uploadpost_config, 'r') as f: upload_cfg = json.load(f) with open(args.templated_config, 'r') as f: templated_cfg = json.load(f) with open(args.oneliners, 'r') as f: oneliners = json.load(f) publisher = SocialMediaPublisher( args.brand, args.news_api_key, upload_cfg, templated_cfg, oneliners ) ``` ### Technical Analysis The program requires the NewsAPI secret to be supplied through `--news-api-key`. Command-line arguments are commonly exposed through process inspection facilities, shell history, scheduler definitions, monitoring products, diagnostic output, and deployment logs. The application does not intentionally print this key, but passing the secret through the process argument vector creates exposure outside the application's direct control. This is avoidable because the key can be loaded through a protected secret channel. ### Attack Path 1. An operator or automated scheduler launches the Skill with `--news-api-key <secret>`. 2. The complete invocation is retained in shell history, orchestration ...[truncated 628 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--news-api-key` argument. 2. Load the key from a protected environment variable, operating-system credential facility, or managed secret store. 3. Configure scheduled jobs to reference the secret provider rather than embedding the key in the command line. 4. Ensure secret values are never printed in logs, exceptions, debug output, or generated result files. 5. Apply least-privilege access controls to the secret and rotate the key if it has previously appeared in command histories or execution logs. 6. Document the secure credential-loading mechanism in `SKILL.md` and provide a clear failure message when the secret is unavailable without displaying its value. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description understates the skill's real behavior: it not only retrieves news, but also renders media, reads local config files, writes output, and publishes content to third-party platforms on a schedule. This mismatch is dangerous because users may approve or invoke the skill without understanding that it performs external posting and broader data handling than the title and summary imply.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares no explicit tool scope even though its documented behavior requires file access, network access, and likely file output. In an agent environment, missing permission boundaries can allow broader-than-expected execution and reduce a user's ability to assess or constrain what the skill is allowed to do.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill automates recurring publication to social-media-related APIs, but the description does not clearly warn users that it can post externally on a schedule. In context, this increases the risk of unintended publication, reputational harm, and unauthorized outbound actions because operators may treat it as simple content generation rather than autonomous posting.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill lists API keys and third-party providers but does not warn that content, metadata, and potentially sensitive business information are transmitted to external services. This is risky because users may supply credentials and content without understanding the data-sharing implications or the trust boundary introduced by those vendors.

External Transmission

Medium
Category
Data Exfiltration
Content
```json
{
  "api_key": "...",
  "endpoint": "https://api.upload-post.com/api/upload_photos"
}
```
Confidence
80% confidence
Finding
The documented UploadPost endpoint confirms that the skill sends data to an external service, which creates an exfiltration and trust-boundary risk even if the behavior is intended. In this skill's context, outbound transmission is central to functionality, but it remains dangerous if not tightly disclosed and constrained because media, generated content, and credentials-related operations interact with third-party infrastructure.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The NewsAPI query explicitly forces English-language content, and the same constraint appears again in the relationship news fetch path. This imposes a language choice without user opt-in and is a natural-language locale policy issue under the stated rules.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The NewsAPI query explicitly forces English-language content, and the file does not provide a way for the user to select another language or acknowledge this restriction. That makes the locale constraint a policy concern rather than an implementation detail.

Tainted flow: 'data' from requests.get (line 47, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
}

        try:
            r = requests.post(
                self.tpl_cfg.get('endpoint', 'https://api.templated.io/v1/render'),
                headers=headers, json=data, timeout=60
            )
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.

External Transmission

Medium
Category
Data Exfiltration
Content
try:
            r = requests.post(
                self.tpl_cfg.get('endpoint', 'https://api.templated.io/v1/render'),
                headers=headers, json=data, timeout=60
            )
            if r.status_code == 200:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'url' from requests.post (line 99, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
if r.status_code == 200:
                url = r.json().get('render_url')
                if url:
                    img = requests.get(url, timeout=30)
                    if img.status_code == 200:
                        return {"url": url, "data": img.content}
        except Exception as e:
Confidence
96% confidence
Finding
The code blindly trusts a `render_url` returned by an external API and immediately fetches it with `requests.get`. If the rendering service or its response is compromised, this can be used for SSRF-style behavior, internal network probing, or downloading unexpected content from attacker-controlled URLs.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code sends image data and captions to a third-party publishing endpoint, which can affect public-facing accounts and transmit user/system-generated data externally. While publishing is part of the skill's purpose, this file provides no confirmation prompt or user-facing disclosure before the outbound post occurs.

Tainted flow: 'data' from requests.get (line 47, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
data = {"caption": caption}

        try:
            r = requests.post(
                self.upload_cfg.get('endpoint', 'https://api.upload-post.com/api/upload_photos'),
                headers=headers, files=files, data=data, timeout=60
            )
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.

External Transmission

Medium
Category
Data Exfiltration
Content
try:
            r = requests.post(
                self.upload_cfg.get('endpoint', 'https://api.upload-post.com/api/upload_photos'),
                headers=headers, files=files, data=data, timeout=60
            )
            return r.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Vague Triggers

Medium
Confidence
90% confidence
Finding
This JSON file contains only generic promotional phrases such as tax reminders, refunds, and compliance benefits, but provides no specific invocation phrases, scope limits, or exclusion conditions. If these strings are used as activation or matching text in a manifest-like context, they are ambiguous and could overlap with common tax-related user speech, increasing the risk of unintended invocation.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The config sets "timezone":"Africa/Johannesburg" directly, which imposes a locale-specific operating context in natural-language-adjacent configuration without indicating that users can choose or override it. The policy allows locale constraints when clearly documented and justified, but this single-line config provides no such justification or opt-in.

Missing User Warnings

Low
Confidence
76% confidence
Finding
The script persists execution results, including posting metadata, to a local JSON file. This is a file write operation, and the file does not include an explicit warning, prompt, or explanatory comment about saving output to disk.

Static analysis

No suspicious patterns detected.