Back to skill

Security audit

90-day-social-generator

Security checks for vulnerabilities and agentic risk

Overview

The skill broadly does what it claims, but it handles client data, API keys, SSH/FTP access, and public uploads with enough unsafe or under-disclosed behavior that users should review it before installing.

Install only after reviewing the data and credential flows. Use least-privilege API keys, avoid passing secrets on the command line, prefer --no-images or local-only image output unless uploads are required, do not use FTP on untrusted networks, and use a dedicated unprivileged SSH key/account with known host verification. Treat Mode A as CRM/customer-data access and confirm client consent before sending generated prompts or business details to third-party AI providers.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (8)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_sitemap.py:33
Finding

Server-Side Request Forgery Through Unvalidated Sitemap and Nested Sitemap URLs

Content
View full analysis
list[str]: req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) Chrome/126.0"}) with urllib.request.urlopen(req, timeout=timeout) as r: body = r.read().decode("utf-8", errors="replace") locs = re.findall(r"\s*(.*?)\s*", body, re.S) # Handle sitemap index (nested sitemaps) - one level deep if len(locs) > 0 and all(l.endswith((".xml", ".xml.gz")) for l in locs): nested = [] for sub in locs[:20]: try: req2 = urllib.request.Request(sub, headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(req2, timeout=timeout) as r2: body2 = r2.read().decode("utf-8", errors="replace") nested.extend(re.findall(r"\s*(.*?)\s*", body2, re.S)) except Exception: continue locs = nested ``` The initial sitemap URL and every nested `` value are passed directly to `urllib.request.urlopen`. There is no validation of the URL scheme, resolved IP address, destination port, redirect target, or relationship between a nested sitemap and the original client domain. `urllib` also follows redirects by default. Consequently, even when `run.sh` constructs an initial HTTPS URL, a remote server can redirect the request to an internal or link-local destination. Nested sitemap entries can directly reference internal services or non-HTTP schemes supported by the runtime. Fetching a client-controlled sitemap is necessary for the declared functionality, but unrestricted access to arbitrary destinations is not. ### Attack Path 1. An attacker supplies a client website they control, or compromises the configured website. 2. The ...[truncated 1254 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_posts.py:36
Finding

LLM API Credentials Can Be Sent to Arbitrary or Plaintext Endpoints

Content
View full analysis
str: key = llm_key if not key: print("ERROR: no LLM key. Pass --llm-key (the only env var in this package is HIGHLEVEL_ACCESS_TOKEN).", file=sys.stderr) sys.exit(2) model = llm_model or "deepseek-chat" base = llm_base or "https://api.deepseek.com/chat/completions" body = json.dumps({ "model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens, "temperature": 0.9, }).encode() req = urllib.request.Request(base, data=body, headers={"Content-Type": "application/json", "Authorization": f"Bearer {key}"}) with urllib.request.urlopen(req, timeout=300) as r: resp = json.loads(r.read()) ``` Equivalent behavior is present in `scripts/generate_images.py:41-56`. The user-controlled `--llm-base` value is used without scheme or host validation, while the supplied API key is always attached as a bearer credential. An `http://` endpoint transmits the key and business content without transport encryption. An attacker-controlled HTTPS endpoint receives the key by design, even if the key was issued for a different provider. The request body also contains the company name, website, city, state, company description, sitemap URLs, and generated-post context. Custom OpenAI-compatible endpoints are part of the declared functionality, but sending a credential to an unrestricted destination without warning or validation exceeds a secure minimum-privilege implementation. ### Attack Path 1. An attacker provides setup instructions or a configuration that changes `--llm-base` to an attacker-controlled URL. 2. The victim invoke ...[truncated 967 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_images.py:162
Finding

FAL API Key Can Be Forwarded to an Unvalidated Polling URL

Content
View full analysis
str: url = "https://queue.fal.run/fal-ai/flux/dev" body = json.dumps({ "prompt": prompt, "image_size": "landscape_4_3", "num_images": 1, "output_format": "png", "enable_safety_checker": False, }).encode() req = urllib.request.Request(url, data=body, method="POST", headers={"Authorization": f"Key {key}", "Content-Type": "application/json"}) try: with urllib.request.urlopen(req, timeout=timeout) as r: resp = json.loads(r.read()) except urllib.error.HTTPError as e: raise RuntimeError(f"fal submit failed: {e.code} {e.read().decode()[:300]}") status_url = resp.get("status_url") or resp.get("response_url") if status_url and resp.get("status") != "COMPLETED": deadline = time.time() + timeout while time.time() < deadline: time.sleep(3) q = urllib.request.Request(status_url, headers={"Authorization": f"Key {key}"}) try: with urllib.request.urlopen(q, timeout=30) as r: st = json.loads(r.read()) ``` A URL taken from the remote JSON response is trusted as a polling destination, and the FAL API key is attached to that destination. The code does not verify that the URL is HTTPS or belongs to an approved FAL domain. Redirect targets are also not validated. ### Attack Path 1. A malicious or compromised API endpoint, proxy, DNS path, or upstream account response supplies a `status_url` controlled by the attacker. 2. The skill creates a request to that URL. 3. It attaches `Authorization: Key `. 4. The attacker records the key and can return a crafted status response to continue influencing proce ...[truncated 448 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_images.py:220
Finding

FTP Upload Mode Transmits Hosting Credentials and Images in Plaintext

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
run.sh:56
Finding

Secrets Passed Through Command-Line Arguments Are Exposed to Process Inspection and Shell History

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/fetch_client.py:21
Finding

HighLevel Contact Retrieval Collects and Stores Unnecessary Personal Data

Content
View full analysis
dict: out = {k: c.get(k) for k in CLIENT_KEYS} out["companyName"] = c.get("companyName") or c.get("businessName") or "" out["customFields"] = c.get("customFields") or {} return out ``` All custom fields are converted and retained: ```python fields = {} for cf in c.get("customFields", []): if not isinstance(cf, dict): continue fid = cf.get("fieldId") or cf.get("id") name = fmap.get(fid, fid) val = cf.get("value") if name and val not in (None, ""): fields[name] = val c["customFields"] = fields ``` The complete normalized record is printed and written to disk: ```python print(json.dumps(client, indent=2)) if args.json_out: with open(args.json_out, "w") as f: json.dump(client, f, indent=2) ``` The post-generation stage only consumes company name, website, city, state, Business Category, and Company Description. Contact ID, first and last names, email, phone, tags, and unrelated custom fields are not needed for the declared social-calendar functionality. The file is created using default process permissions, which may result in a world-readable file under a permissive umask. Printing the record can additionally expose it to terminal logs or automation output. ### Attack Path 1. Mode A searches HighLevel for a contact. 2. The returned record includes personal contact details and potentially sensitive custom fields. 3. The tool copies those values into `data/client.json` and prints them to standard output. 4. Another local user, backup process, CI artifact collector, or terminal logging system accesses ...[truncated 477 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/generate_images.py:294
Finding

SSH Upload Implicitly Uses the User's Default Private Key and Trusts Unknown Hosts on First Contact

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_images.py:90
Finding

Predictable Temporary Image Files Permit Symlink-Based File Overwrite

Content
View full analysis
.png` pointing to a file writable by the victim. 4. The image response is decoded using `open(path, "wb")`. 5. The target file is truncated and replaced with image bytes. 6. The later cleanup may remove the temporary link, but the target-file corruption has already occurred. ### Impact Assessment The attacker can overwrite files writable by the user executing the skill. This can cause data loss and may enable code execution if the victim owns a subsequently executed script, configuration file, or other security-sensitive writable target. The code runs without privilege escalation, so it cannot overwrite files the invoking u ...[truncated 35 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (37)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

If the skill says it fetches a client's sitemap and integrates with image generation and HighLevel, but actually relies on pre-supplied files or omits those functions, users can make unsafe assumptions about data provenance, completeness, and where data is sent. Security decisions based on inaccurate workflow descriptions can lead to oversharing client data or using the tool in environments where it is not appropriate.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

If the skill says it fetches a client's sitemap and integrates with image generation and HighLevel, but actually relies on pre-supplied files or omits those functions, users can make unsafe assumptions about data provenance, completeness, and where data is sent. Security decisions based on inaccurate workflow descriptions can lead to oversharing client data or using the tool in environments where it is not appropriate.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

If the skill says it fetches a client's sitemap and integrates with image generation and HighLevel, but actually relies on pre-supplied files or omits those functions, users can make unsafe assumptions about data provenance, completeness, and where data is sent. Security decisions based on inaccurate workflow descriptions can lead to oversharing client data or using the tool in environments where it is not appropriate.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 45)May include surrounding context.

md
#!/usr/bin/env bash
# 90-day social calendar pipeline. ONE command per client.
#
# MODE A - HighLevel connected (needs HIGHLEVEL_ACCESS_TOKEN in .env or env):
#   ./run.sh --email client@email.com [--count 45] [--start-date YYYY-MM-DD]
#
# MODE B - No HighLevel needed (client details given directly):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 78)May include surrounding context.

md
#!/usr/bin/env bash
# 90-day social calendar pipeline. ONE command per client.
#
# MODE A - HighLevel connected (needs HIGHLEVEL_ACCESS_TOKEN in .env or env):
#   ./run.sh --email client@email.com [--count 45] [--start-date YYYY-MM-DD]
#
# MODE B - No HighLevel needed (client details given directly):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · run.sh (reported line 4)May include surrounding context.

sh
#!/usr/bin/env bash
# 90-day social calendar pipeline. ONE command per client.
#
# MODE A - HighLevel connected (needs HIGHLEVEL_ACCESS_TOKEN in .env or env):
#   ./run.sh --email client@email.com [--count 45] [--start-date YYYY-MM-DD]
#
# MODE B - No HighLevel needed (client details given directly):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.py (reported line 9)May include surrounding context.

python
#!/usr/bin/env bash
# 90-day social calendar pipeline. ONE command per client.
#
# MODE A - HighLevel connected (needs HIGHLEVEL_ACCESS_TOKEN in .env or env):
#   ./run.sh --email client@email.com [--count 45] [--start-date YYYY-MM-DD]
#
# MODE B - No HighLevel needed (client details given directly):

Missing User Warnings

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The script supports FTP credentials and passes them to the image workflow without warning, implying use of a protocol that transmits credentials and content insecurely unless wrapped externally. In a skill that may upload client-branded assets, this creates a realistic risk of credential theft and content interception or tampering.

Content

No source excerpt is available for this finding.

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/common.py (reported line 22)May include surrounding context.

python
def load_env() -> dict:
    env = dict(os.environ)
    dotenv = os.path.join(REPO_ROOT, ".env")
    if os.path.exists(dotenv):
        for line in open(dotenv):

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.py (reported line 23)May include surrounding context.

python
def load_env() -> dict:
    env = dict(os.environ)
    dotenv = os.path.join(REPO_ROOT, ".env")
    if os.path.exists(dotenv):
        for line in open(dotenv):
            line = line.strip()

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The module docstring explicitly states there is 'No SSH, no server upload,' but the implementation later adds both SSH and FTP upload capabilities. This mismatch can mislead operators and reviewers into running the script with a lower threat expectation, causing unintended external transfer of generated content and use of local SSH credentials.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

Defaulting to ~/.ssh/id_rsa means the script will automatically attempt to use a local private key for remote upload when --ssh-host is set, increasing the chance of unintended credential use. In this skill context, users may run automation with privileged workstation or server keys present, so silent fallback expands the blast radius if the destination is misconfigured or untrusted.

Content

Scanner excerpt · scripts/generate_images.py (reported line 300)May include surrounding context.

python
user, host = ssh_host.split("@", 1)
        else:
            user, host = (args.ssh_user or "root"), ssh_host
        key = args.ssh_key or os.path.expanduser("~/.ssh/id_rsa")
        upload_fn = lambda local, remote: ssh_upload(host, user, key, local, remote)
        upload_host = host
        print(f"    upload via SSH: {user}@{host}:{args.ssh_remote_dir}", file=sys.stderr)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README explicitly instructs users to send client business details, website-derived topics, and potentially descriptive company information to third-party LLM and image APIs, but it provides no warning about privacy, consent, retention, or vendor data handling. In a client-services workflow, that omission can lead to unauthorized disclosure of customer or CRM-derived data to external providers.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

Mode A describes pulling client details from HighLevel by email and API token, including custom fields such as business category and company description, without an explicit warning that this accesses customer CRM data and may then feed it into downstream generation steps. This increases the risk of users processing sensitive customer or tenant data without proper authorization, notice, or least-privilege controls.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill advertises capabilities that involve environment variables, network access, shell execution, and writing files, but it does not declare any explicit tool scope or permissions boundaries. In a skill ecosystem, missing scope declarations reduce transparency and can allow broader-than-expected execution, increasing the risk of secret exposure, unintended network access, or host-side side effects.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Uploading generated images to a public host and embedding public URLs in the CSV creates an external publication step that may expose client branding assets, campaign timing, or sensitive imagery if users are unaware. The danger is elevated in this context because the skill is designed for client work and encourages use of hosting credentials, so lack of explicit warning can lead to accidental public exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script accepts sensitive secrets such as LLM and image API keys directly via command-line arguments. Command-line secrets are commonly exposed through shell history, process listings, audit logs, and CI job output, making credential leakage likely on multi-user systems or managed runners.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

When run with --email, the script fetches client information from HighLevel and later retrieves the client's sitemap over the network. Although progress is logged, there is no user disclosure that client identifiers and business data will be transmitted to external services as part of the workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The workflow sends sitemap-derived website content and client details into post generation and potentially image generation services, with optional third-party APIs and remote upload destinations. In this skill context, the data flow crosses trust boundaries and can expose proprietary or personal business information to external providers without explicit operator acknowledgement.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script prints that image generation is 'local, no upload', but it conditionally forwards SSH/FTP parameters to the image step, enabling remote transfer of generated assets. This mismatch can mislead operators into sending client-derived content to external infrastructure they did not intend to use, which is a security and privacy issue in an automation skill handling client data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code writes normalized client information, including contact and business fields, to a path supplied by the user. Although the write is logged after completion, there is no prior warning or confirmation that potentially sensitive client data will be persisted to disk or that an existing file may be overwritten.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

The script sends prompts derived from post content to an external LLM endpoint (api.deepseek.com by default). In a client-content generation workflow, this is a real data egress path that may disclose proprietary or sensitive content outside the local environment.

Content

Scanner excerpt · scripts/generate_images.py (reported line 46)May include surrounding context.

python
if not key:
        raise RuntimeError("no LLM key (pass --llm-key; the only env var here is HIGHLEVEL_ACCESS_TOKEN)")
    model = llm_model or "deepseek-chat"
    base = llm_base or "https://api.deepseek.com/chat/completions"
    body = json.dumps({
        "model": model,
        "messages": [{"role": "user", "content": prompt}],

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/generate_images.py (reported line 215)May include surrounding context.

python
import subprocess
    conn = ["-i", key, "-o", "ConnectTimeout=15", "-o", "StrictHostKeyChecking=accept-new"]
    dest = f"{user}@{host}:{remote_path}"
    r = subprocess.run(["scp"] + conn + [local, dest], capture_output=True, text=True)
    if r.returncode != 0:
        raise RuntimeError(f"scp failed: {r.stderr[-300:]}")

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · README.md (reported line 56)May include surrounding context.

md
ap.add_argument("--image-base-url", default="", help="https://yourdomain.com/social to fill CSV imageUrls")
    ap.add_argument("--llm-key", default="")
    ap.add_argument("--llm-model", default="")
    ap.add_argument("--llm-base", default="", help="OpenAI-compatible endpoint (default DeepSeek; use https://api.openai.com/v1/chat/completions for ChatGPT/OpenAI keys)")
    ap.add_argument("--gemini-key", default="")
    ap.add_argument("--fal-key", default="")
    ap.add_argument("--kling-ak", default="")

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · README.md (reported line 111)May include surrounding context.

md
ap.add_argument("--image-base-url", default="", help="https://yourdomain.com/social to fill CSV imageUrls")
    ap.add_argument("--llm-key", default="")
    ap.add_argument("--llm-model", default="")
    ap.add_argument("--llm-base", default="", help="OpenAI-compatible endpoint (default DeepSeek; use https://api.openai.com/v1/chat/completions for ChatGPT/OpenAI keys)")
    ap.add_argument("--gemini-key", default="")
    ap.add_argument("--fal-key", default="")
    ap.add_argument("--kling-ak", default="")