Back to skill

Security audit

Larry — Autonomous TikTok Slideshow Agent

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it ships real-looking credentials and can use them to create TikTok/Postiz drafts through network calls with weak destination safeguards.

Review before installing. Do not use the bundled config.json values; rotate any exposed keys/passwords, replace them with your own least-privilege credentials, restrict file permissions, and only point postiz_base_url at a trusted HTTPS or verified local instance. Treat --auto/cron as unattended social-media publishing workflow and use dry-run/manual review until you are comfortable with the drafts, logs, and external uploads.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
config.json:2
Finding
Production Credentials Stored in Plaintext Configuration<![CDATA[ ## Vulnerability Details **File Location**: `config.json:2-5` **Vulnerability Type**: Hardcoded secrets and plaintext credentials **Risk Level**: High ### Vulnerable Code ```json { "nvidia_api_key": "[REDACTED — production-looking NVIDIA API key]", "postiz_api_key": "[REDACTED — production-looking Postiz API key]", "postiz_email": "alexander@krueger.vip", "postiz_password": "[REDACTED — plaintext Postiz password]", "postiz_base_url": "http://localhost:4007/api" } ``` The secret values have been redacted from this report to avoid further credential disclosure. The audited file contains the complete plaintext values. ### Technical Analysis The distributed `config.json` contains a production-looking NVIDIA API key, a Postiz API token, a Postiz account email address, and a plaintext Postiz password. These values are directly usable application credentials rather than documented placeholders. Any person or process with read access to the project directory, source archive, backup, build artifact, or repository history can recover these credentials. Secret exposure is especially serious for Postiz because the account may have access to connected TikTok publishing integrations. The example configuration also promotes storing all credentials in a regular JSON file: ```json { "nvidia_api_key": "nvapi-YOUR_KEY_HERE", "postiz_api_key": "YOUR_POSTIZ_API_KEY", "postiz_email": "you@example.com", "postiz_password": "your_postiz_password" } ``` This design does not provide encryption, access isolation, automatic rotation, or protection against accidental source-control commits. ### Attack Path 1. An attacker downloads the Skill package, obtains a repository clone, reads a backup, or gains local read access to the project. 2. The attacker opens `config.json` and extracts the NVIDIA API key, Postiz API token, email address, and password. 3. The attacker uses the NVIDIA key to consume API resources. 4. The attacker uses the Postiz token or a ...[truncated 882 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Immediately revoke and rotate the exposed NVIDIA key, Postiz API key, and Postiz password. 2. Remove `config.json` from all distributed packages and source-control history. 3. Add `config.json` and other secret-bearing files to `.gitignore`. 4. Retain only a placeholder-only `config.example.json`. 5. Load secrets from environment variables, an operating-system credential store, or a dedicated secret manager. 6. Avoid retaining the Postiz account password when a narrowly scoped API token can perform the required operations. 7. Restrict token permissions to only media upload, draft creation, and analytics retrieval where supported. 8. Apply restrictive file permissions, such as `0600`, to any unavoidable local secret file. 9. Add automated secret scanning to pre-commit hooks and CI pipelines. 10. Review repository history, release archives, logs, and backups for copies of the exposed values. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/postiz.py:10
Finding
Postiz Credentials, Session Cookies, and API Tokens Can Be Sent over Plain HTTP<![CDATA[ ## Vulnerability Details **File Locations**: `config.json:6`, `config.example.json:6`, `scripts/postiz.py:10-24`, `scripts/postiz.py:32-65`, `scripts/postiz.py:81-95`, `scripts/postiz.py:108-127` **Vulnerability Type**: Insecure transmission of sensitive information and unrestricted destination configuration **Risk Level**: High ### Vulnerable Code Default configuration: ```json "postiz_base_url": "http://localhost:4007/api" ``` Password-based authentication: ```python def _get_auth_headers(base_url: str, config: dict) -> dict: """Login + Cookie als Header zurückgeben (Postiz Self-Hosted Auth).""" resp = requests.post( f"{base_url}/auth/login", json={"email": config.get("postiz_email", ""), "password": config.get("postiz_password", ""), "provider": "LOCAL"}, timeout=10 ) if not resp.ok: raise Exception(f"Postiz Login fehlgeschlagen: {resp.status_code}") token = resp.cookies.get("auth") if not token: raise Exception("Kein Auth-Cookie erhalten") return {"Cookie": f"auth={token}"} ``` Cookie-authenticated content creation: ```python base_url = config.get("postiz_base_url", "https://api.postiz.com/v1") auth = _get_auth_headers(base_url, config) headers = {**auth, "Content-Type": "application/json"} resp = requests.post( f"{base_url}/posts", headers=headers, json=payload, timeout=30 ) ``` Cookie-authenticated media upload: ```python with open(img_path, "rb") as f: resp = requests.post( f"{base_url}/media/upload-simple", headers=auth, files={"file": (path.name, f, "image/jpeg")}, timeout=60 ) ``` Bearer-token analytics request: ```python base_url = config.get("postiz_base_url", "https://api.postiz.com/v1") api_key = config["postiz_api_key"] headers = {"Authorization": f"Bearer {api_key}"} resp = requests.get( f"{base_url}/posts/{post_id}/analytics", headers=headers, timeout=10 ) ``` ...[truncated 2671 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for every non-loopback Postiz destination. 2. Permit HTTP only through an explicit development-only option and only when the parsed hostname is a verified loopback address. 3. Parse the URL with `urllib.parse.urlparse` and reject unsupported schemes, embedded credentials, unexpected ports, fragments, and malformed hosts. 4. Maintain an allowlist of approved Postiz hostnames or require an administrator-provided trusted endpoint. 5. Disable redirects for authentication requests or validate every redirect target before forwarding sensitive headers or bodies. 6. Prefer narrowly scoped API-token authentication over transmitting the account password on each posting run. 7. Do not manually propagate session cookies to unrelated hosts. 8. Use a configured `requests.Session` with clear authentication and redirect boundaries. 9. Require valid TLS certificates; do not introduce `verify=False`. 10. Document secure reverse-proxy and TLS setup for self-hosted Postiz deployments. 11. Rotate the currently exposed credentials after deploying the secure transport changes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_slides.py:21
Finding
Predictable Shared Temporary Paths Permit Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Locations**: `scripts/generate_slides.py:21-32`, `scripts/generate_slides.py:79-82` **Vulnerability Type**: Insecure temporary-file creation and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python output_dir = Path("/tmp") / f"larry_{datetime.now().strftime('%Y%m%d_%H%M%S')}" output_dir.mkdir(parents=True, exist_ok=True) base_scene = _build_base_scene(concept, portal) slides = concept.get("slides", []) image_paths = [] for i, slide in enumerate(slides): prompt = _build_image_prompt(base_scene, slide, portal, i) img_path = output_dir / f"slide_{i+1:02d}.png" success = _generate_single(prompt, img_path, api_key) ``` The image is then written through another predictable path: ```python jpg_path = out_path.with_suffix(".jpg") portrait.save(str(jpg_path), "JPEG", quality=95) out_path.unlink(missing_ok=True) jpg_path.rename(out_path) ``` ### Technical Analysis The code creates a temporary directory in the shared `/tmp` namespace using a timestamp with one-second precision. Its name and the image filenames are predictable: - `/tmp/larry_YYYYMMDD_HHMMSS` - `slide_01.jpg` - `slide_02.jpg` - Additional sequential files `mkdir(..., exist_ok=True)` accepts a directory that already exists rather than requiring the process to create a new, private directory. Image saving then opens predictable filenames without exclusive creation or symbolic-link checks. On a multi-user system, another local user can predict the execution timestamp, pre-create the directory, and place a symbolic link at an expected `.jpg` path. Pillow's save operation may follow that link and overwrite the linked target if the Skill process has permission to write it. The subsequent rename does not prevent the initial write through the attacker-controlled link. ### Attack Path 1. A local attacker observes or predicts when the scheduled Skill will run. 2. The attacker creates `/tmp/larry_YYYYMMDD_HHMMSS` before the S ...[truncated 1069 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the timestamp-derived path with `tempfile.TemporaryDirectory()` or `tempfile.mkdtemp()`. 2. Ensure the temporary directory is created atomically with permissions restricted to the current user, normally `0700`. 3. Do not use `exist_ok=True` for security-sensitive temporary directories. 4. Create output files with exclusive semantics so pre-existing files cause failure. 5. Reject symbolic links before writing and, where supported, use `O_NOFOLLOW`. 6. Keep the original secure temporary directory handle or path for the complete generation and overlay workflow. 7. Run the Skill as an unprivileged dedicated account. 8. Clean up generated temporary content after successful upload or failure. 9. If generated files must persist, move them atomically from the private temporary directory into an application-owned directory. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:28
Finding
Unpinned Runtime Dependencies Are Installed without Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:28-29` **Vulnerability Type**: Unpinned third-party dependencies and missing package-integrity controls **Risk Level**: Low ### Vulnerable Code ```bash # 2. Install Python dependencies pip3 install pillow requests ``` ### Technical Analysis The documented installation command retrieves mutable latest versions of `pillow` and `requests` from whichever Python package index is active in the user's environment. The project does not provide: - Exact dependency versions. - A lockfile. - Package hashes. - A trusted index requirement. - A reproducible environment definition. This does not prove that either named package is malicious. Both are established packages. The risk arises because the installed artifacts can change after the Skill has been reviewed, and local pip configuration may redirect installation to an untrusted package index. Package installation can execute build-system or installation code with the privileges of the user running pip. Lack of version and hash controls therefore creates a supply-chain exposure and makes deployments non-reproducible. ### Attack Path 1. The user follows the documented setup command. 2. The local pip configuration resolves packages through a compromised or attacker-controlled index, or a future package release is compromised. 3. Pip downloads an artifact that was not part of the audited Skill package. 4. Malicious build or installation behavior executes with the user's package-installation privileges. 5. The compromised dependency can subsequently access the same files, environment variables, API credentials, and network resources available to the Skill. ### Impact Assessment Potential impact includes: - Arbitrary code execution during dependency installation or import. - Theft of NVIDIA and Postiz credentials available to the process. - Modification of generated content or outbound requests. - Compromise of the user account running installation. - ...[truncated 264 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a reviewed requirements file with exact versions. 2. Generate and record cryptographic hashes for every direct and transitive dependency. 3. Install with an integrity-enforcing command such as: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` 4. Use a lock-generation tool that resolves and pins transitive dependencies. 5. Explicitly document the trusted package index and avoid untrusted `extra-index-url` settings. 6. Install dependencies inside a dedicated virtual environment. 7. Periodically scan pinned versions for known vulnerabilities and update them through a reviewed process. 8. Consider producing a signed, reproducible deployment artifact for scheduled or production use. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (31)

Tainted flow: 'headers' from os.environ.get (line 56, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
payload = {"prompt": prompt, "width": 1024, "height": 1024, "steps": NVIDIA_STEPS}

    try:
        resp = requests.post(NVIDIA_ENDPOINT, headers=headers, json=payload, timeout=120)
        if resp.status_code == 200:
            raw_bytes = _b64.b64decode(resp.json()["artifacts"][0]["base64"])
            img = Image.open(BytesIO(raw_bytes)).convert("RGB")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Session Persistence

Medium
Category
Rogue Agent
Content
```
# Manual single post:
"Larry, create a TikTok post for [portal] about [topic]"

# Autonomous mode (via cron):
python3 ~/.openclaw/skills/larry/scripts/larry.py --portal my-portal --auto
Confidence
79% confidence
Finding
The documented autonomous cron-based mode implies ongoing operation using persisted local configuration, API keys, and connected TikTok/Postiz sessions to continue posting across runs. In this context, session persistence is not inherently malicious, but it is security-relevant because compromise of the host, config, or connected Postiz environment could let an attacker post content or abuse affiliated accounts without additional user interaction.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill documents an autonomous posting mode and persistent performance logging, but it does not present a clear, explicit warning to users that enabling --auto will schedule or upload content to TikTok and store ongoing activity data. This creates a real safety and consent issue: users may trigger external actions and data retention without fully understanding the consequences, increasing the risk of unintended posting, reputational harm, or privacy/compliance problems.

Natural-Language Policy Violations

Medium
Confidence
83% confidence
Finding
This manifest/config contains multiple natural-language values that target German-language brands and hashtags, such as German domains and German hashtags, but provides no indication that the skill offers a language or locale choice. Under the policy, forcing a specific language or locale without user opt-in can be a natural-language policy violation unless the regional constraint is clearly documented and justified.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This JSON content is entirely authored in German, including the title, slide text, caption hook, and final caption, with no indication that the user can choose another language or that the skill is restricted to German-speaking users. The policy explicitly calls for flagging language or locale constraints that are imposed without user opt-in or clear justification.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This manifest-like JSON contains all user-facing title, slide, caption, and CTA text in German, indicating a fixed language for the skill's generated content. The file does not offer any language or locale choice, nor does it document that the skill is intentionally restricted to German users or a Germany-specific compliance context.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The top-level documentation describes the skill as generating images via NVIDIA FLUX.1-schnell, while the actual endpoint constant at L013 targets black-forest-labs/flux.1-dev. Because the documentation identifies a different model than the one actually called, this is a direct intent-code divergence.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The top-level natural-language description is entirely in German and presents the skill behavior as German by default, with no indication that users may choose another language or that the tool is region-specific. This matches the language/locale policy violation category because it imposes a specific language without opt-in.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The function documentation says it generates images via NVIDIA FLUX.1-schnell, but the configured endpoint at L013 is explicitly for flux.1-dev and _generate_single also documents FLUX.1-dev usage. This is an active contradiction in the code documentation about which model/service is invoked.

External Transmission

Medium
Category
Data Exfiltration
Content
payload = {"prompt": prompt, "width": 1024, "height": 1024, "steps": NVIDIA_STEPS}

    try:
        resp = requests.post(NVIDIA_ENDPOINT, headers=headers, json=payload, timeout=120)
        if resp.status_code == 200:
            raw_bytes = _b64.b64decode(resp.json()["artifacts"][0]["base64"])
            img = Image.open(BytesIO(raw_bytes)).convert("RGB")
Confidence
80% confidence
Finding
The function transmits generated prompts derived from `concept`, `portal`, and slide data to an external third-party service. In a skill context, those fields may contain sensitive or proprietary user content, so sending them off-box without explicit consent, minimization, or policy checks can create a real data exposure risk even though the destination appears legitimate.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script writes draft post data to the queue directory during dry runs and later appends performance data to a persistent log file, but these data-affecting writes are not disclosed in the CLI help or through any confirmation before execution. Although there is a runtime log after the draft write, there is no prior warning that running the skill will create or modify local files.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The non-dry-run path sends post data, including captions and image paths, to an external posting integration via upload_to_tiktok. While posting is part of the skill's purpose, the current interface does not clearly warn at invocation time that live execution will transmit content to an external service unless the user infers it from the script name and comments.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module title, docstrings, and all user-facing status messages are written in German, indicating the skill is designed to operate in a specific language without offering any user opt-in or alternative locale. The policy for this audit flags language-forcing behavior unless the locale restriction is explicitly justified as region-specific.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring describes a narrowly scoped integration that uploads TikTok carousels as drafts for later manual publishing. However, the same file also contains `check_stats`, which reads a local performance log, fetches post analytics from the API, and rewrites the log with updated metrics. That is a different operational intent than the stated upload-only behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
def _get_auth_headers(base_url: str, config: dict) -> dict:
    """Login + Cookie als Header zurückgeben (Postiz Self-Hosted Auth)."""
    resp = requests.post(
        f"{base_url}/auth/login",
        json={"email": config.get("postiz_email", ""),
              "password": config.get("postiz_password", ""),
Confidence
85% confidence
Finding
This code transmits email and password credentials to a configurable base_url without validating the destination. If an attacker can influence configuration, credentials can be sent to an attacker-controlled endpoint, resulting in account compromise and possible broader service access.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The function sends email and password from configuration to a remote authentication endpoint, which is a safety-relevant network operation involving credentials. While the function has an internal docstring, there is no explicit user-facing disclosure, confirmation, or warning that stored credentials will be used and transmitted over the network.

External Transmission

Medium
Category
Data Exfiltration
Content
Uploaded Slideshow als TikTok Draft via Postiz.
    Self-Hosted: Cookie-basierte Auth statt Bearer Token.
    """
    base_url = config.get("postiz_base_url", "https://api.postiz.com/v1")
    account_id = portal.get("tiktok_account_id", "")

    auth = _get_auth_headers(base_url, config)
Confidence
88% confidence
Finding
The function uses a configurable remote API base URL with a production default and then authenticates and uploads media to that endpoint. In an agent-skill context, allowing arbitrary external endpoints materially increases the risk of data exfiltration of credentials and media if configuration is tampered with.

External Transmission

Medium
Category
Data Exfiltration
Content
}

    try:
        resp = requests.post(
            f"{base_url}/posts",
            headers=headers,
            json=payload,
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code reads local image files and uploads them to the Postiz media endpoint, which transfers user data off the local system. Although errors are printed, there is no upfront disclosure in the code warning that local media contents will be sent to an external or self-hosted service.

External Transmission

Medium
Category
Data Exfiltration
Content
Ruft Performance-Daten für alle gespeicherten Posts ab.
    Wird täglich aufgerufen um Hook-Performance zu tracken.
    """
    base_url = config.get("postiz_base_url", "https://api.postiz.com/v1")
    api_key = config["postiz_api_key"]
    headers = {"Authorization": f"Bearer {api_key}"}
Confidence
84% confidence
Finding
This function retrieves analytics using an API key from a configurable remote endpoint and writes returned data to local storage. If the endpoint is attacker-controlled, the API key can be disclosed and untrusted response data can be persisted, creating credential exposure and integrity risks.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This function reads a bearer API key, retrieves remote analytics for stored posts, and overwrites a local JSON log with updated metrics. The operation combines credential use, network access, and file writes, but the code provides no explicit user-facing disclosure beyond internal comments/docstrings.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The prompt explicitly instructs the model that the generated hook must be 'Auf Deutsch', which hard-codes a language choice. The policy allows locale/language constraints only when the user is offered a choice or the constraint is clearly documented and justified; this file does not show such opt-in or justification.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
Antworte NUR mit dem Hook-Text, nichts weiter."""

    try:
        result = subprocess.run(
            ["claude", "-p", prompt],
            capture_output=True, text=True, timeout=30
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
With no manifest available, there is no declared scope that would justify launching external processes. The code uses subprocess.run to invoke the local Claude CLI, which is a materially broader capability than pure in-process research/content generation logic and can have side effects outside the module's own data handling.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
}}"""

    try:
        result = subprocess.run(
            ["claude", "-p", prompt],
            capture_output=True, text=True, timeout=45
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.