Back to skill

Security audit

tiktok-carousel

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it can send Postiz API credentials and uploaded content to an arbitrary environment-configured endpoint without validation or clear disclosure.

Install only if you trust the runtime environment and review POSTIZ_API_URL before any upload. Prefer leaving it unset for the official Postiz endpoint, use a least-privileged Postiz API key, upload only intended images, and consider pinning dependencies before production use.

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

T09 · Insecure Skill Coding Practices

Error
Location
postiz_api_integration.py:6
Finding

Configurable Postiz Endpoint Can Disclose API Credentials and Uploaded Content

Content
View full analysis

Vulnerability Details

File Location: postiz_api_integration.py, lines 6-51
Vulnerability Type: Unvalidated credential-bearing network destination
Risk Level: High

Vulnerable Code

python
POSTIZ_API_KEY = os.getenv("POSTIZ_API_KEY")
POSTIZ_API_URL = os.getenv("POSTIZ_API_URL", "https://api.postiz.com/v1")


def _auth_headers() -> Dict[str, str]:
    if not POSTIZ_API_KEY:
        raise PostizError("POSTIZ_API_KEY is not set in environment")
    return {"Authorization": f"Bearer {POSTIZ_API_KEY}"}


def upload_media_to_postiz(file_path: str) -> Dict[str, Any]:
    """Uploads a local image file to Postiz and returns the parsed JSON response.

    Returns a dict with at least an `id` or `url` depending on API.
    """
    p = Path(file_path)
    if not p.exists():
        raise PostizError(f"file not found: {file_path}")

    url = f"{POSTIZ_API_URL}/media/upload"
    headers = _auth_headers()
    with p.open('rb') as fh:
        files = {'file': fh}
        resp = requests.post(url, headers=headers, files=files)
    try:
        resp.raise_for_status()
    except Exception as e:
        raise PostizError(f"upload failed: {e} - {resp.text}")
    return resp.json()


def create_tiktok_draft(media_ids: List[str], caption: str, privacy_level: str = "SELF_ONLY") -> Dict[str, Any]:
    url = f"{POSTIZ_API_URL}/tiktok/create_draft"
    headers = _auth_headers()
    headers.update({"Content-Type": "application/json"})
    payload = {
        "type": "carousel",
        "media_ids": media_ids,
        "caption": caption,
        "privacy_level": privacy_level,
    }
    resp = requests.post(url, headers=headers, json=payload)

Technical Analysis

POSTIZ_API_URL is accepted directly from the process environment without validating its scheme or destination host. The same destination receives an Authorization: Bearer header containing the Postiz API key, sel ...[truncated 1447 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse POSTIZ_API_URL with urllib.parse.urlparse.
  • Require the https scheme and reject embedded credentials, fragments, and malformed hostnames.
  • Allowlist the official Postiz API hostname by default.
  • If self-hosted Postiz installations must be supported, require explicit opt-in and display or log the resolved destination before transmitting credentials.
  • Use separate, least-privileged API credentials for self-hosted or development endpoints.
  • Add connection and read timeouts to every request.
  • Consider loading configuration at invocation time rather than module import time so validated configuration is consistently applied.
  • Avoid including untrusted response bodies in errors where those messages could later be exposed to logs or users.

T09 · Insecure Skill Coding Practices

Warning
Location
tiktok_content_gen.py:45
Finding

Remote Image Download Lacks Timeouts, Size Limits, and Content Validation

Content
View full analysis

Vulnerability Details

File Location: tiktok_content_gen.py, lines 45-55
Vulnerability Type: Unbounded and unvalidated remote content download
Risk Level: Medium

Vulnerable Code

python
if not image_url and isinstance(response, dict):
    # try known keys
    data = response.get('data')
    if data and isinstance(data, list) and data[0].get('url'):
        image_url = data[0]['url']

if image_url:
    image_data = requests.get(image_url).content
    with open(out, 'wb') as f:
        f.write(image_data)
    return str(out)

Technical Analysis

The URL returned by the remote image-generation service is fetched without:

  • Connection or read timeouts
  • An HTTP status check
  • Streaming and maximum-response-size enforcement
  • HTTPS or destination validation
  • Content-Type verification
  • Image decoding, format, or dimension validation before storage

Accessing .content buffers the complete response in memory before writing it to disk. A malicious or compromised service can therefore return a very large or non-terminating response, causing memory exhaustion or indefinite blocking. The response is saved with a .png extension regardless of its actual type and is subsequently opened by Pillow for the first slide, exposing the image parser to malformed input.

Attack Path

  1. The image API, its returned storage URL, DNS resolution, or a network path is compromised.
  2. The generator receives a URL under the attacker's control.
  3. The attacker returns an oversized response, an extremely slow response, or malformed non-image data.
  4. requests.get(image_url).content waits without a timeout and buffers the complete response.
  5. The process may exhaust memory, consume disk space, or remain blocked.
  6. For the first slide, add_text_overlay() subsequently opens the downloaded file with Pillow, potentially triggering expensive parsing or a defect in the installed image decod ...[truncated 455 chars]
Remediation
View remediation

Remediation Suggestions

  • Use explicit connect and read timeouts, for example timeout=(5, 30).
  • Call raise_for_status() before processing the response body.
  • Use stream=True and copy data in chunks while enforcing a strict maximum byte count.
  • Require HTTPS and, where practical, allowlist image-storage hosts expected from the OpenAI API.
  • Reject redirects to unsupported schemes or untrusted destinations.
  • Validate the response Content-Type, while recognizing that it cannot replace content inspection.
  • Decode the image with Pillow before accepting it, call verify(), enforce permitted formats, and reject excessive pixel dimensions.
  • Save to a temporary file and atomically replace the destination only after successful validation.
  • Delete partial downloads when validation or transfer fails.

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Dependency Versions Are Not Reproducibly Pinned

Content
View full analysis

Vulnerability Details

File Location: requirements.txt, lines 1-4
Vulnerability Type: Uncontrolled dependency resolution
Risk Level: Medium

Vulnerable Code

text
openai>=1.0.0
requests>=2.28.0
Pillow>=10.0.0
python-dotenv>=1.0.0

Technical Analysis

Every dependency uses an open-ended lower-bound constraint. A future installation can therefore resolve to versions that were not present during this audit, along with changing transitive dependencies. This prevents reproducible builds and permits newly published, compromised, or behaviorally incompatible releases to enter the runtime without review.

The package names themselves correspond to known packages, so dependency confusion or typosquatting is not demonstrated. The risk arises from unrestricted future version selection and the absence of a lock file or hashes. python-dotenv also appears unused in the reviewed source, unnecessarily increasing supply-chain exposure.

Attack Path

  1. A future package or transitive dependency release satisfies one of the broad >= constraints.
  2. That release is compromised, malicious, or contains a newly introduced security defect.
  3. A user follows the documented pip install -r requirements.txt installation command.
  4. The package resolver selects the unreviewed release.
  5. Package build or installation logic runs with the privileges of the installing user, and imported package code later runs with the privileges and accessible secrets of the Skill process.

This is a supply-chain exposure path rather than evidence that any dependency currently listed is malicious.

Impact Assessment

A compromised dependency can potentially execute code with the installing user's privileges and access files, network resources, and environment variables available to the process, including OPENAI_API_KEY and POSTIZ_API_KEY. The exact scope depends on the user's privileges and installation ...[truncated 13 chars]

Remediation
View remediation

Remediation Suggestions

  • Generate and commit a reviewed lock file containing exact versions.
  • Use hashes for all direct and transitive packages, such as a hash-locked requirements file generated by pip-tools.
  • Install with hash enforcement, for example pip install --require-hashes -r requirements.lock.
  • Update dependencies through a controlled process that includes changelog review, vulnerability scanning, and automated tests.
  • Pin the supported Python version because dependency resolution can vary by interpreter version and platform.
  • Remove python-dotenv unless the project begins using it.
  • Periodically scan the resolved dependency graph with tools such as pip-audit.
  • Perform installations in an isolated virtual environment under a non-privileged account.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (15)

Tainted flow: 'url' from os.getenv (line 42, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
97% confidence
Finding

The upload endpoint is built from POSTIZ_API_URL and is used to transmit local file contents with an Authorization header. If the environment variable is tampered with, local images and the API token can be sent to an attacker-controlled server, which is especially dangerous because this function reads arbitrary local files provided to it.

Content

Scanner excerpt · postiz_api_integration.py (reported line 33)May include surrounding context.

python
headers = _auth_headers()
    with p.open('rb') as fh:
        files = {'file': fh}
        resp = requests.post(url, headers=headers, files=files)
    try:
        resp.raise_for_status()
    except Exception as e:

Tainted flow: 'url' from os.getenv (line 42, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The destination URL is derived from the POSTIZ_API_URL environment variable and then used directly in requests.post while sending captions and authenticated API requests. If an attacker can influence the environment, they can redirect traffic and the Bearer token and posted content to an attacker-controlled endpoint, creating an SSRF/exfiltration risk.

Content

Scanner excerpt · postiz_api_integration.py (reported line 51)May include surrounding context.

python
"caption": caption,
        "privacy_level": privacy_level,
    }
    resp = requests.post(url, headers=headers, json=payload)
    try:
        resp.raise_for_status()
    except Exception as e:

Tainted flow: 'image_url' from os.getenv (line 50, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · tiktok_content_gen.py (reported line 53)May include surrounding context.

python
image_url = data[0]['url']

    if image_url:
        image_data = requests.get(image_url).content
        with open(out, 'wb') as f:
            f.write(image_data)
        return str(out)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `requirements.txt` — Python dependencies.

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 · postiz_api_integration.py (reported line 7)May include surrounding context.

python
from typing import List, Dict, Any, Optional

POSTIZ_API_KEY = os.getenv("POSTIZ_API_KEY")
POSTIZ_API_URL = os.getenv("POSTIZ_API_URL", "https://api.postiz.com/v1")


class PostizError(Exception):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code uploads local image files to a remote Postiz API and later sends captions and media identifiers to create a TikTok draft, but there is no confirmation prompt, logging, or user-facing disclosure around the data transmission. The docstrings describe functionality, but they do not warn that local files and user-provided content are being transmitted to an external service.

Content

No source excerpt is available for this finding.

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 · postiz_api_integration.py (reported line 51)May include surrounding context.

python
"caption": caption,
        "privacy_level": privacy_level,
    }
    resp = requests.post(url, headers=headers, json=payload)
    try:
        resp.raise_for_status()
    except Exception as e:

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The skill reads an API key from the environment and uses it for authenticated external API calls, but there is no visible warning, comment, or user-facing disclosure that credentials are required and will be used for outbound requests. For code files, access to sensitive environment variables should have some form of disclosure unless clearly covered elsewhere.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
96% confidence
Finding

The dependency specification uses a lower-bound version constraint (>=) rather than pinning to an exact version, which makes builds non-reproducible and can cause different environments to resolve different package versions. This increases supply-chain and stability risk because a vulnerable or incompatible future release could be installed without review.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
openai>=1.0.0
requests>=2.28.0
Pillow>=10.0.0
python-dotenv>=1.0.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
99% confidence
Finding

The requests dependency is not pinned to an exact version, so installations may resolve to different releases over time. Because requests has multiple known advisories and the manifest does not constrain to a reviewed fixed version, the environment could inadvertently install a vulnerable release or introduce regressions.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
openai>=1.0.0
requests>=2.28.0
Pillow>=10.0.0
python-dotenv>=1.0.0

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 manifest does not pin requests, and the package has known advisories, so there is no way to verify from this file alone whether the installed version is affected. In practice, this means the deployment could resolve to a vulnerable release, particularly in rebuilds or fresh environments.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
99% confidence
Finding

Pillow is specified with a minimum version only, which leaves the actual installed version dependent on resolver behavior and repository state at install time. Since Pillow has had multiple security issues, this creates uncertainty about whether deployed environments are protected from known vulnerabilities.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
openai>=1.0.0
requests>=2.28.0
Pillow>=10.0.0
python-dotenv>=1.0.0

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +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

Pillow has known security advisories, but because the requirement is not pinned, the actual version installed cannot be verified from the manifest. That uncertainty is dangerous for an image-processing library because historically such packages have included memory-safety and resource-consumption issues that may be exploitable through crafted files.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

python-dotenv is unpinned, which means builds are not reproducible and different deployments may pull different package versions. This is a supply-chain hygiene issue that becomes more important because the package has advisories and may interact with sensitive configuration data.

Content

Scanner excerpt · requirements.txt (reported line 4)May include surrounding context.

text
openai>=1.0.0
requests>=2.28.0
Pillow>=10.0.0
python-dotenv>=1.0.0

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding

python-dotenv has known advisories, and the unpinned requirement prevents verification of whether the installed version is safe. Although impact depends on how the library is used, leaving the version unconstrained creates avoidable risk around configuration-file handling.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.