Back to skill

Security audit

openlens-skill

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it ships a real-looking API key and gives broad, weakly warned control over where prompts, media, and credentials are sent.

Review before installing. Do not rely on the bundled config.json key, revoke or replace any exposed key, and only enter your own API keys when the base URL is an HTTPS endpoint you trust. Avoid sending sensitive prompts, images, or videos, especially on shared Streamlit hosts, because the skill forwards them to configured providers and may save or temporarily retain generated or uploaded media locally.

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
config.json:2
Finding
Hardcoded API Credential Distributed in Project Configuration<![CDATA[ ## Vulnerability Details **File Location**: `config.json:2-5` **Vulnerability Type**: Hardcoded secret / plaintext credential exposure **Risk Level**: High ### Vulnerable Code ```json { "video_api_url": "https://api.onlypixai.com/v1", "video_api_key": "sk-px-97d6f29fb4c79b6f21b7ae000d9dab669a4fa1ab", "text_api_url": "https://api.onlypixai.com/v1", "text_api_key": "sk-px-97d6f29fb4c79b6f21b7ae000d9dab669a4fa1ab", "text_model": "pa/grok-4-1-fast-non-reasoning", "default_video_model": "video/wan2.6-t2v", "default_save_path": "./outputs" } ``` The credential is consumed automatically by the CLI: ```python config = load_config() if args.config and os.path.exists(args.config): with open(args.config, 'r') as f: config = json.load(f) video_api_url = config.get("video_api_url", "") video_api_key = config.get("video_api_key", "") text_api_url = config.get("text_api_url", "") text_api_key = config.get("text_api_key", "") ``` ### Technical Analysis A bearer credential with the structure of a real API token is committed in plaintext. The same token is reused for both text and video services, increasing its effective scope and the consequences of disclosure. Anyone who can download the Skill package, inspect its repository, access a build artifact, or read a previously published version can recover the token without executing the software. Removing it only from the latest file is insufficient if it remains in source-control history, package caches, release archives, or mirrors. The application sends this token in an HTTP `Authorization: Bearer` header. Consequently, possession of the token may be sufficient to invoke the associated provider APIs independently of the Skill. ### Attack Path 1. An attacker obtains the repository, Skill archive, or published release. 2. The attacker reads `config.json`. 3. The attacker extracts the bearer token. 4. The attacker submits requests directly to the configured text or video ...[truncated 702 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Revoke the exposed token immediately and issue a replacement. 2. Remove the token from all current source files, release artifacts, package registries, and downloadable archives. 3. Rewrite source-control history where feasible, while treating the token as compromised regardless of history cleanup. 4. Replace `config.json` with a placeholder-only example file and exclude operational configuration through `.gitignore`. 5. Load credentials from environment variables, a platform secret store, or a dedicated credential manager. 6. Use separate, least-privilege credentials for text and video APIs rather than sharing one token. 7. Add automated secret scanning to pre-commit hooks and CI pipelines. 8. Configure provider-side spending limits, rate limits, expiration, and credential rotation. 9. Avoid printing credentials or including them in exception messages and diagnostics. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
skill_main.py:146
Finding
Unrestricted API Endpoints Can Receive Credentials and Private Media<![CDATA[ ## Vulnerability Details **File Location**: `skill_main.py:146-192, 245-261, 362-364`; equivalent behavior also appears in `app.py:209-248, 307-327, 342-348`, `cli.py:53-115, 187-199`, and `openlens-web/app.py:231-307, 347-353` **Vulnerability Type**: Missing endpoint validation and transport-security enforcement **Risk Level**: High ### Vulnerable Code The public Skill API accepts an arbitrary base URL: ```python def run_openlens_task( url: str, api_key: str, model_id: str, prompt: str, task_type: str = "T2V", video_specs: dict | None = None, image_path: str | None = None, video_path: str | None = None, system_prompt: str = "", outputs_dir: str | None = None, ) -> dict: ``` That URL is used to send bearer credentials and prompts: ```python def _call_t2i(base_url: str, api_key: str, model_id: str, prompt: str, resolution: str, steps: int) -> Path: payload = _build_t2i_payload(model_id, prompt, resolution, steps) url = f"{base_url.rstrip('/')}/images/generations" log.info("T2I request → %s model=%s", url, model_id) resp = requests.post(url, headers=_auth_headers(api_key), json=payload, timeout=60) resp.raise_for_status() image_url = resp.json()["data"][0]["url"] dest = _output_path("T2I", model_id, "png") return _download(image_url, dest) def _call_t2t(base_url: str, api_key: str, model_id: str, prompt: str, system_prompt: str = "") -> str: url = f"{base_url.rstrip('/')}/chat/completions" messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) messages.append({"role": "user", "content": prompt}) payload = {"model": model_id, "messages": messages, "temperature": 0.7, "max_tokens": 2048} log.info("T2T request → %s model=%s", url, model_id) resp = requests.post(url, headers=_auth_headers(api_key), json=payl ...[truncated 3452 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse base URLs with `urllib.parse.urlparse` before use. 2. Require `https` for remote endpoints. 3. Permit plaintext HTTP only for an explicit development mode restricted to loopback addresses. 4. Reject embedded credentials, malformed hostnames, unexpected schemes, and ambiguous URLs. 5. Block loopback, link-local, multicast, and private-network destinations unless the user explicitly enables private API access. 6. Display the normalized destination hostname before transmitting a credential or media file. 7. Bind saved credentials to a specific normalized origin so a credential configured for one provider cannot silently be sent to another. 8. Consider a configurable hostname allowlist for managed deployments. 9. Require explicit confirmation when the destination changes. 10. Apply separate API credentials per provider and generation function. 11. Continue using certificate verification and do not introduce `verify=False`. 12. Enforce upload-size limits and clearly inform users that prompts and media are sent to the selected third-party provider. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
skill_main.py:386
Finding
Predictable Shared Temporary Files Permit Collisions and Symlink-Based Overwrites<![CDATA[ ## Vulnerability Details **File Location**: `skill_main.py:386-398` **Vulnerability Type**: Unsafe temporary-file creation **Risk Level**: Medium ### Vulnerable Code ```python image_path_input = None video_path_input = None if task_type == "I2V": uploaded = st.file_uploader("Source image", type=["jpg","jpeg","png","webp"], key="i2v_up") if uploaded: tmp = Path("/tmp") / uploaded.name tmp.write_bytes(uploaded.read()) image_path_input = str(tmp) if task_type == "V2V": uploaded = st.file_uploader("Source video", type=["mp4","mov"], key="v2v_up") if uploaded: tmp = Path("/tmp") / uploaded.name tmp.write_bytes(uploaded.read()) video_path_input = str(tmp) ``` ### Technical Analysis The application derives a temporary path directly from the uploaded filename and writes it into the system-wide `/tmp` directory. It does not: - Generate an unpredictable server-side filename. - Create the file atomically with exclusive-create semantics. - Check for symbolic links. - Assign a private per-request directory. - Remove the file after submission. - Enforce an application-level upload-size limit. On a multi-user host, another local process can predict a filename and create a symbolic link at that location before the upload is written. `Path.write_bytes()` follows the normal filesystem path and does not provide safe temporary-file semantics. Filename collisions can also cause one session to overwrite or consume another session's media. Files remain in `/tmp` after processing, exposing sensitive user media to later processes where host permissions permit access and allowing repeated uploads to consume storage. ### Attack Path A local symlink or collision attack can proceed as follows: 1. The attacker predicts or influences an uploaded filename, such as `source.jpg`. 2. Before the victim upload is processed, the attacker creates `/tmp/source.jpg` as a symbolic link ...[truncated 1035 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use `tempfile.TemporaryDirectory` or `tempfile.NamedTemporaryFile` with unpredictable names. 2. Create a separate private temporary directory for each request or session. 3. Preserve only an allowlisted suffix derived from the validated media type; do not use the client filename as the server filename. 4. Ensure files are created atomically with exclusive semantics and restrictive permissions. 5. Delete temporary files and directories in a `finally` block after submission, including failure and timeout paths. 6. Add explicit upload-size limits before writing data to disk. 7. Validate file signatures rather than relying only on filename extensions. 8. Avoid reopening a mutable path after creation; retain a securely opened file descriptor where practical. 9. Configure the deployment account with minimal filesystem permissions and a dedicated temporary directory unavailable to other users. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (64)

Ssd 1

High
Confidence
98% confidence
Finding
The skill explicitly markets itself as bypassing content filtering restrictions and operating with no safety middleware, which is a strong red flag under adversarial review. In the context of an AI media generation portal, this materially increases the likelihood of abuse for prohibited, harmful, or policy-evading content generation and indicates deliberate removal of safeguards rather than an incidental omission.

Natural-Language Policy Violations

High
Confidence
97% confidence
Finding
Advertising 'no content filtering, pure pass-through' is a safety-bypass posture that encourages use of the tool without moderation or policy enforcement, increasing the likelihood it will be used to generate abusive, unsafe, or policy-violating content. In an AI media generation skill, that context makes the statement more dangerous because the tool directly brokers user prompts to generation APIs and even supports prompt enhancement.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly advertises 'no content filtering - pure pass-through to your API' and 'Local Save' behavior, but it does not clearly warn users that prompts, images, and generated outputs may be transmitted to third-party endpoints and written to local disk. In a skill context, this can lead to unsafe handling of sensitive data, policy-violating content generation, or unexpected filesystem side effects because users are encouraged to use a tool with reduced safeguards.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation instructs users to send prompts and API keys to a remote third-party endpoint without any explicit privacy or security warning about off-device transmission. Because prompts may contain sensitive data and API keys are high-value secrets, omission of this warning can mislead users about confidentiality and increase the chance of accidental disclosure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs users to place API credentials in config.json and describes sending prompts and optional image URLs to external text and video APIs, but it does not clearly warn about the privacy and credential-handling implications. Users may unknowingly expose sensitive prompts, media inputs, or secrets through local plaintext storage and third-party transmission.

External Transmission

Medium
Category
Data Exfiltration
Content
"age_enter": "I am 18+ - Enter", "age_exit": "Exit", "age_redirecting": "Redirecting...",
        "main_title": "OpenLens", "main_subtitle": "Multi-Modal AI | T2I T2V I2V V2V",
        "config_title": "Configuration", "global_settings": "Global Settings",
        "global_api_url": "API Base URL", "global_api_url_placeholder": "https://api.openai.com/v1",
        "text_model": "Text Model (Prompt)", "text_api_key": "Text API Key", "text_api_key_placeholder": "sk-...",
        "text_model_name": "Model Name", "text_model_placeholder": "gpt-4o",
        "t2i_model": "Image (T2I)", "t2i_api_key": "T2I API Key", "t2i_api_key_placeholder": "sk-...",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"age_enter": "I am 18+ - Enter", "age_exit": "Exit", "age_redirecting": "Redirecting...",
        "main_title": "OpenLens", "main_subtitle": "Multi-Modal AI | T2I T2V I2V V2V",
        "config_title": "Configuration", "global_settings": "Global Settings",
        "global_api_url": "API Base URL", "global_api_url_placeholder": "https://api.openai.com/v1",
        "text_model": "Text Model (Prompt)", "text_api_key": "Text API Key", "text_api_key_placeholder": "sk-...",
        "text_model_name": "Model Name", "text_model_placeholder": "gpt-4o",
        "t2i_model": "Image (T2I)", "t2i_api_key": "T2I API Key", "t2i_api_key_placeholder": "sk-...",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"age_enter": "I am 18+ - Enter", "age_exit": "Exit", "age_redirecting": "Redirecting...",
        "main_title": "OpenLens", "main_subtitle": "Multi-Modal AI | T2I T2V I2V V2V",
        "config_title": "Configuration", "global_settings": "Global Settings",
        "global_api_url": "API Base URL", "global_api_url_placeholder": "https://api.openai.com/v1",
        "text_model": "Text Model (Prompt)", "text_api_key": "Text API Key", "text_api_key_placeholder": "sk-...",
        "text_model_name": "Model Name", "text_model_placeholder": "gpt-4o",
        "t2i_model": "Image (T2I)", "t2i_api_key": "T2I API Key", "t2i_api_key_placeholder": "sk-...",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"age_enter": "I am 18+ - Enter", "age_exit": "Exit", "age_redirecting": "Redirecting...",
        "main_title": "OpenLens", "main_subtitle": "Multi-Modal AI | T2I T2V I2V V2V",
        "config_title": "Configuration", "global_settings": "Global Settings",
        "global_api_url": "API Base URL", "global_api_url_placeholder": "https://api.openai.com/v1",
        "text_model": "Text Model (Prompt)", "text_api_key": "Text API Key", "text_api_key_placeholder": "sk-...",
        "text_model_name": "Model Name", "text_model_placeholder": "gpt-4o",
        "t2i_model": "Image (T2I)", "t2i_api_key": "T2I API Key", "t2i_api_key_placeholder": "sk-...",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The UI claims 'No API Keys stored', but the app places API keys into Streamlit session state for the lifetime of the session. Even if this is not long-term persistent storage, the statement is inaccurate and can mislead users into entering sensitive credentials under a false privacy assumption.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The localized Chinese footer repeats the same misleading claim that API keys are not stored, while they are retained in session state during app use. Misrepresenting secret handling is a security issue because it undermines informed consent and safe credential use.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The Japanese footer also states that API keys are not stored, but the application keeps them in Streamlit session state while the app runs. This discrepancy can cause users to trust the app with secrets they might not otherwise provide.

External Transmission

Medium
Category
Data Exfiltration
Content
headers = make_headers(api_key)
    payload = {"model": model, "messages":[{"role":"system","content":"Enhance prompt with cinematic details."},{"role":"user","content":prompt}], "temperature":0.7}
    try:
        r = requests.post(f"{api_url}/chat/completions", headers=headers, json=payload, timeout=60)
        if r.status_code != 200: return handle_error(r)
        return r.json()["choices"][0]["message"]["content"]
    except Exception as e:
Confidence
90% confidence
Finding
This call transmits user prompt content and the bearer API key to an external service. External transmission is expected for an AI client, but in this skill it is made riskier by the fully configurable api_url, which allows secrets and content to be sent to arbitrary destinations.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The application sends user prompts, uploaded media, media URLs, and API keys to a user-configurable external endpoint without an explicit disclosure or trust boundary warning. Because the base URL is configurable, the app can be pointed at arbitrary third-party servers, increasing the risk of sensitive data exfiltration or accidental disclosure.

External Transmission

Medium
Category
Data Exfiltration
Content
headers = make_headers(api_key)
    payload = {"model": model, "prompt": prompt, "n":1, "size":"1024x1024"}
    try:
        r = requests.post(f"{api_url}/images/generations", headers=headers, json=payload, timeout=120)
        if r.status_code != 200: return handle_error(r)
        return {"url": r.json()["data"][0]["url"], "type": "image", "prompt": prompt}
    except Exception as e:
Confidence
89% confidence
Finding
This request sends the user's generation prompt and authorization token to an external image-generation endpoint. In context, the transmission is part of normal functionality, but it still presents a real disclosure risk because the destination is configurable and the user is not clearly warned about data sharing.

External Transmission

Medium
Category
Data Exfiltration
Content
"""提交异步任务"""
    headers = make_headers(api_key)
    try:
        r = requests.post(f"{api_url}{endpoint}", headers=headers, json=payload, timeout=60)
        if r.status_code != 200: return handle_error(r), None
        data = r.json()
        return data.get("task_id") or data.get("id"), data
Confidence
94% confidence
Finding
This generic task submission sends arbitrary payloads, which in this app can include prompts plus image/video content or base64-encoded uploaded files, to an external endpoint along with bearer credentials. Because media may be sensitive and the endpoint is configurable, this creates a substantial exfiltration and privacy risk if misconfigured or abused.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The top-level documentation states the CLI is a 'pure pass-through' to the API, implying user input is forwarded unchanged. However, when --refine is enabled, the code sends the prompt through a separate text model with an instruction to transform and enhance it, then submits the modified prompt for video generation.

External Transmission

Medium
Category
Data Exfiltration
Content
for endpoint in [f"{api_url}/chat/completions", f"{api_url}/responses"]:
        try:
            resp = requests.post(endpoint, headers=headers, json=payload, timeout=60)
            if resp.status_code == 200:
                data = resp.json()
                if "choices" in data:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
payload["input"]["img_url"] = image_url
    
    log_msg(f"Submitting to {api_url}/video/generations...")
    resp = requests.post(f"{api_url}/video/generations", headers=headers, json=payload, timeout=30)
    
    if resp.status_code == 200:
        data = resp.json()
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
93% confidence
Finding
The skill description advertises image/video generation via external APIs, and the config schema includes API URLs and API keys. The manifest does not warn that prompts and possibly media inputs may be transmitted to third-party endpoints, which is relevant to user privacy and data handling.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The manifest explicitly highlights absence of content filtering, which increases the likelihood of unsafe, offensive, or policy-sensitive outputs. There is no adjacent warning or disclaimer to inform users about this behavior or recommend appropriate use.

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

Medium
Category
Data Flow
Content
resp = requests.post(url, headers=_headers(api_key), json=payload, timeout=60)
    resp.raise_for_status()
    image_url = resp.json()["data"][0]["url"]
    return requests.get(image_url, timeout=60).content


def submit_video_task(base_url: str, api_key: str, model: str,
Confidence
92% confidence
Finding
The app blindly dereferences an image URL returned by an external API and fetches it server-side. If the configured or compromised upstream API returns an internal, link-local, or cloud-metadata URL, this creates a server-side request forgery path and can expose internal services or sensitive data through the Streamlit host.

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

Medium
Category
Data Flow
Content
elif video_bytes:
        files = {"video": ("source.mp4", video_bytes, "video/mp4")}
        data = {**payload, "model": model}
        resp = requests.post(url, headers=headers, data=data, files=files, timeout=60)
    else:
        resp = requests.post(url, headers=_headers(api_key), json=payload, 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.

Tainted flow: 'video_url' from requests.get (line 327, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
or (data.get("videos") or [{}])[0].get("video_url")
                    or data.get("output", {}).get("url")
                )
                return requests.get(video_url, timeout=120).content
            if status in ("FAILED", "ERROR", "CANCELLED"):
                raise RuntimeError(f"Task failed with status: {status}")
            time.sleep(5)
Confidence
93% confidence
Finding
The app polls a remote task endpoint, extracts a `video_url` from the JSON response, and then fetches that URL without validation. A malicious or untrusted API endpoint can supply attacker-controlled URLs, enabling SSRF from the application server to internal network resources or sensitive metadata endpoints.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The UI sends prompts and uploaded media to user-specified external endpoints but does not clearly disclose that this content will leave the local app/session. In a tool handling arbitrary images, videos, and unrestricted generation prompts, this can cause unintended disclosure of sensitive or proprietary user content.

Static analysis

No suspicious patterns detected.