Back to skill

Security audit

Bailian Studio

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its cloud media purpose, but its unvalidated endpoint overrides and upload/download paths could expose API credentials or private media if misconfigured or influenced.

Review this skill before installing. Use it only with non-sensitive images and text unless you accept that content being sent to Aliyun DashScope and, for local images, uploaded to OSS. Do not use untrusted DASHSCOPE_BASE_URL, --base-url, or OSS_ENDPOINT values; prefer the documented Aliyun HTTPS endpoints, narrowly scoped credentials, private buckets or short-lived URLs, and an isolated virtual environment with pinned dependencies.

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
scripts/image_generate.py:188
Finding
Unrestricted DashScope Endpoint Override Can Expose API Credentials and User Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/env.py:39-40`, `scripts/image_generate.py:145-145, 188-188, 206-206`, `scripts/ocr_text.py:50-50, 61-61, 70-73`, `scripts/tts_speak.py:53-53, 77-83` **Vulnerability Type**: Unvalidated credential-bearing API endpoint override **Risk Level**: High ### Vulnerable Code ```python # scripts/env.py:39-40 def get_region_base_url(env_path: Optional[Path] = None) -> str: return _get_value("DASHSCOPE_BASE_URL", env_path) or DEFAULT_BASE_URL ``` ```python # scripts/image_generate.py response = dashscope.MultiModalConversation.call( api_key=get_dashscope_key(env_path=env_path), model=model, messages=messages, result_format="message", stream=False, watermark=False, prompt_extend=True, negative_prompt=negative_prompt or DEFAULT_NEGATIVE_PROMPT, size=build_size(width, height), ) parser.add_argument( "--base-url", default=None, help="Override DashScope base URL", ) dashscope.base_http_api_url = ( args.base_url or get_region_base_url(env_path=args.config) ) ``` ```python # scripts/ocr_text.py resp = dashscope.MultiModalConversation.call( api_key=get_dashscope_key(), model=model, messages=messages, ocr_options={"task": "text_recognition"}, ) parser.add_argument("--base-url", default=None) if args.base_url: dashscope.base_http_api_url = args.base_url else: dashscope.base_http_api_url = get_region_base_url() ``` ```python # scripts/tts_speak.py resp = dashscope.audio.qwen_tts.SpeechSynthesizer.call( api_key=get_dashscope_key(), **kwargs, ) parser.add_argument("--base-url", default=None) if args.base_url: dashscope.base_http_api_url = args.base_url else: dashscope.base_http_api_url = get_region_base_url() ``` ### Technical Analysis The three service clients permit the DashScope base URL to be supplied through either a command-line argument or the `DASHSCOPE_BASE_URL` configuration value. The supplied URL is assign ...[truncated 1965 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--base-url` from normal user-facing operation unless endpoint customization is a required feature. 2. Enforce HTTPS for every credential-bearing API request. 3. Parse the endpoint with `urllib.parse.urlparse` and reject: - HTTP and other non-HTTPS schemes - Embedded usernames or passwords - Missing hostnames - IP literals - Loopback, link-local, private, and reserved destinations 4. Maintain an explicit allowlist of supported DashScope hostnames, such as the documented Aliyun endpoint. 5. Apply the same validation to `DASHSCOPE_BASE_URL` loaded from configuration. 6. Require a separate explicit opt-in for nonstandard enterprise endpoints and avoid sending production credentials until the endpoint has been approved. 7. Add tests confirming that HTTP, unapproved hosts, localhost, private IP addresses, and malformed URLs are rejected. 8. Use separate restricted API keys with minimum quotas and permissions, and rotate any key that may have been exposed through an untrusted endpoint. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/oss_upload.py:19
Finding
Arbitrary or Plaintext OSS Endpoints Can Receive Uploaded Local Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/oss_upload.py:19-23, 32-33, 35-54` **Vulnerability Type**: Unvalidated storage endpoint and optional plaintext transport **Risk Level**: Medium ### Vulnerable Code ```python def _normalize_endpoint(endpoint: str) -> str: if endpoint.startswith("http://") or endpoint.startswith("https://"): return endpoint return f"https://{endpoint}" ``` ```python def upload_image(image_path: Path, object_key: Optional[str] = None) -> str: cfg = get_oss_config() access_key = cfg["access_key"] secret_key = cfg["secret_key"] bucket_name = cfg["bucket"] endpoint = cfg["endpoint"] image_path = Path(image_path) if not image_path.exists(): raise RuntimeError(f"Image not found: {image_path}") suffix = image_path.suffix or ".png" object_key = object_key or ( f"bailian-studio/{int(time.time())}-{image_path.stem}{suffix}" ) auth = oss2.Auth(access_key, secret_key) bucket = oss2.Bucket( auth, _normalize_endpoint(endpoint), bucket_name, ) bucket.put_object_from_file(object_key, str(image_path)) return _build_public_url(endpoint, bucket_name, object_key) ``` ### Technical Analysis The endpoint normalization function accepts an explicit `http://` URL unchanged. It also accepts any HTTPS hostname without checking that it belongs to the expected Aliyun OSS service. When local-image OCR or image-to-image generation is used, the path supplied by the caller is passed to `upload_image`, which uploads the entire file to that endpoint. The function verifies only that the path exists; it does not verify that it is a regular file, that its contents are an image, or that its size is reasonable. The OSS secret key is used locally to generate authentication data and is not necessarily transmitted verbatim. However, a hostile endpoint can receive the selected file, access-key identifiers, signed requests, metadata, and ...[truncated 1317 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject all OSS endpoints that do not use HTTPS. 2. Validate the parsed hostname against approved Aliyun OSS hostname patterns or a deployment-specific allowlist. 3. Reject embedded credentials, malformed ports, IP literals, and local or private network destinations. 4. Resolve hostnames carefully and protect against DNS rebinding where nonstandard endpoints are supported. 5. Require the upload path to be a regular file and reject symbolic links where they are not required. 6. Validate the file using its content signature rather than relying only on the extension. 7. Enforce an upload size limit and permit only explicitly supported image formats. 8. Display or log the destination hostname before uploading to a nondefault endpoint, and require explicit user confirmation in interactive contexts. 9. Use narrowly scoped OSS credentials limited to one bucket and object prefix. 10. Add tests for HTTP rejection, unapproved hosts, symbolic links, non-image files, oversized files, and private network destinations. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/image_generate.py:119
Finding
Unvalidated API-Provided Media URLs Permit SSRF and Unbounded Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/image_generate.py:119-140, 157-164`; `scripts/tts_speak.py:36-40, 51-59, 62-67` **Vulnerability Type**: Server-side request forgery, unrestricted redirects, and resource exhaustion **Risk Level**: Medium ### Vulnerable Code ```python # scripts/image_generate.py def extract_image_url(response: Any) -> str: output = ( response.get("output") if isinstance(response, dict) else getattr(response, "output", None) ) if isinstance(output, dict): for key in ("results", "images"): items = output.get(key) if isinstance(items, list) and items: first = items[0] if isinstance(first, dict) and first.get("url"): return first["url"] choices = output.get("choices") if isinstance(choices, list) and choices: message = ( choices[0].get("message") if isinstance(choices[0], dict) else None ) content = ( message.get("content") if isinstance(message, dict) else None ) if isinstance(content, list): for item in content: if isinstance(item, dict) and item.get("image"): return item["image"] if output.get("url"): return output["url"] raise RuntimeError( f"Unexpected image generation response format: {response}" ) ``` ```python # scripts/image_generate.py def download_image(url: str, output_path: Path, timeout: int) -> Path: """Download an image URL to disk.""" response = requests.get(url, timeout=timeout) response.raise_for_status() ensure_parent(output_path) output_path.write_bytes(response.content) return output_path ``` ```python # scripts/tts_speak.py def _extract_audio_url(resp) -> str: try: return resp["o ...[truncated 3774 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate every response URL before making a request. 2. Require HTTPS and permit only documented DashScope or Aliyun media hostnames. 3. Resolve the hostname and reject loopback, private, link-local, multicast, unspecified, and reserved addresses for both IPv4 and IPv6. 4. Disable automatic redirects or validate every redirect target using the same policy. 5. Use `stream=True` and enforce a strict maximum download size while iterating over chunks. 6. Reject responses whose declared `Content-Length` exceeds the configured limit. 7. Validate `Content-Type`, but do not rely on it alone. 8. Verify file signatures and decode media with a constrained image or WAV parser before saving or playing it. 9. Configure maximum image dimensions, audio duration, and decompressed size. 10. Keep ffmpeg fully patched and consider sandboxing playback with reduced privileges and restricted network access. 11. Write downloads to a temporary file first and atomically rename them only after successful validation. 12. Add tests for private-address URLs, redirects to private destinations, oversized bodies, incorrect MIME types, malformed files, and download timeouts. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unpinned Dependencies and Missing Integrity Verification Reduce Supply-Chain Security<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-4` **Vulnerability Type**: Mutable dependency resolution without integrity pinning **Risk Level**: Low ### Vulnerable Code ```text dashscope>=1.24.0 oss2>=2.19.1 requests>=2.31.0 pytest>=8.0.0 ``` ### Technical Analysis All dependencies use open-ended minimum-version constraints. A future installation may therefore resolve to package versions that were not reviewed or tested with this project. The file also provides no cryptographic hashes to verify downloaded distributions. Python packages can execute code during installation, import, and normal runtime. If a future release is compromised, removed and replaced, or introduces insecure behavior, users following the documented installation command may execute it without any change to the audited repository. The package names are consistent with the libraries used by the source code. The audit found no suspicious package index, direct URL, obvious typo-squatting name, or confirmed malicious dependency. This finding concerns reproducibility and missing supply-chain hardening rather than evidence that the currently named packages are malicious. Including `pytest` in the runtime requirements also unnecessarily expands the production dependency set. ### Attack Path 1. A user runs the documented command `pip install -r requirements.txt`. 2. The package resolver selects the newest versions satisfying the lower bounds at installation time. 3. Those versions may differ from the versions originally reviewed and tested. 4. Installation or imported package code executes with the privileges of the user performing the installation. 5. If a selected release is compromised, the package can access files, credentials, environment variables, and network resources available to that user. ### Impact Assessment The potential impact is bounded by the privileges of the account installing or running the dependencies. If installation is performed as an adm ...[truncated 288 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every runtime dependency to a reviewed exact version. 2. Generate a lock file using a reproducible dependency-management workflow. 3. Record cryptographic hashes and install with pip's `--require-hashes` option. 4. Separate runtime dependencies from development and test dependencies; move `pytest` to a development requirements file. 5. Use a controlled package index or an approved internal mirror in production and CI. 6. Enable automated vulnerability and release monitoring. 7. Review dependency updates before regenerating the lock file. 8. Run installation and application execution as an unprivileged user in an isolated virtual environment or container. 9. Retain provenance information for downloaded artifacts and prefer signed or otherwise verifiable releases where available. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (30)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
This documentation explicitly indicates that local images are uploaded to Alibaba Cloud OSS before being passed onward, but the top-level description does not make that data-transfer behavior prominent. Hidden or underemphasized third-party upload behavior is dangerous because users may provide sensitive local files believing processing is limited to DashScope or local execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This documentation explicitly indicates that local images are uploaded to Alibaba Cloud OSS before being passed onward, but the top-level description does not make that data-transfer behavior prominent. Hidden or underemphasized third-party upload behavior is dangerous because users may provide sensitive local files believing processing is limited to DashScope or local execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This documentation explicitly indicates that local images are uploaded to Alibaba Cloud OSS before being passed onward, but the top-level description does not make that data-transfer behavior prominent. Hidden or underemphasized third-party upload behavior is dangerous because users may provide sensitive local files believing processing is limited to DashScope or local execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This documentation explicitly indicates that local images are uploaded to Alibaba Cloud OSS before being passed onward, but the top-level description does not make that data-transfer behavior prominent. Hidden or underemphasized third-party upload behavior is dangerous because users may provide sensitive local files believing processing is limited to DashScope or local execution.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
This documentation explicitly indicates that local images are uploaded to Alibaba Cloud OSS before being passed onward, but the top-level description does not make that data-transfer behavior prominent. Hidden or underemphasized third-party upload behavior is dangerous because users may provide sensitive local files believing processing is limited to DashScope or local execution.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
def read_prompt(prompt: Optional[str]) -> str:
    """Read prompt from argument or stdin."""
    if prompt and prompt.strip():
        return prompt.strip()
    if not sys.stdin.isatty():
        stdin_prompt = sys.stdin.read().strip()
        if stdin_prompt:
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README explicitly states that local images are uploaded to OSS before OCR or image-to-image processing, and that generated files are written locally, but it does not clearly warn users about external data transmission, persistence, or possible sensitivity of uploaded content. This can lead users to unintentionally send confidential local images to a cloud service or leave generated outputs on disk without realizing the privacy and data-handling implications.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documentation describes shell commands, environment-variable access, local file reads/writes, and network interactions, but it declares no explicit tool scope or permissions. In an agent setting, this creates an overbroad and under-specified capability surface, making it easier for the skill to access sensitive files, secrets, or external networks without clear user or platform constraints.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation states that local images are uploaded to OSS first, but it does not present this as a prominent warning near the user workflow. This is dangerous because users may unknowingly send sensitive local documents, screenshots, or personal images to third-party storage and downstream AI services, creating confidentiality and compliance risks.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The image-to-image instructions show local reference image usage without an explicit warning that the file will be uploaded to external services. In this context, under-disclosure is especially risky because users often treat local creative assets as private, while the workflow transmits them off-device and may expose them through cloud storage or service logs.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The design sends arbitrary user-provided text to an external Bailian TTS service but does not mention any user notice, consent, or data-handling warning. This can cause inadvertent disclosure of sensitive text if users assume synthesis is local, especially in a skill that reads from configurable environment-based integrations and is intended for easy command-line use.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The plan explicitly sends user-provided text to an external TTS service but does not require any user-facing disclosure, consent prompt, or documentation warning that the text leaves the local environment. This can cause unintentional disclosure of sensitive prompts, secrets, or personal data if users assume the CLI is purely local audio playback.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
When a local image path is supplied, the code automatically uploads that file via upload_image() to obtain a remote URL for img2img. This implicit exfiltration of local file contents is risky because users may not realize a local image will leave their machine and be stored or processed by external infrastructure.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script sends the user-provided prompt and any optional reference image to external services: DashScope for generation and potentially other remote endpoints for image handling. In a skill context, prompts and images may contain secrets, personal data, or proprietary material, and the CLI provides no explicit disclosure, confirmation, or data-classification guard before transmission.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
When a local image is provided, the script uploads that image to an external service and then submits it for OCR, but this file gives no user-facing disclosure, confirmation, or warning that image contents may leave the local environment. This can lead to unintended exfiltration of sensitive documents, IDs, screenshots, or regulated data, especially because OCR inputs often contain private text by design.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The helper constructs a public URL for uploaded content, and the surrounding upload flow can publish user-provided images to object storage without evidence of access controls or user consent. If sensitive or private images are processed, this can lead to unintended public exposure, link sharing, and long-lived data disclosure.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The file adds a generic OSS upload capability that is broader than the stated skill purpose of calling Bailian via DashScope for OCR, TTS, and image generation. This increases the skill's data-handling scope and creates a path for user images to be transferred to external storage, which can expose sensitive content and expand the attack surface beyond what a user would reasonably expect.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This function retrieves cloud credentials and transmits local image data to OSS over the network with no visible warning, prompt, or disclosure to the user. In a skill context, silent exfiltration of user-supplied media to third-party storage is risky because users may assume processing happens only through the described Bailian API path, not persistent external storage.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script sends arbitrary user-provided text to external DashScope TTS services and then fetches the resulting audio over the network without any user-facing disclosure at runtime. In a skill context, users may unknowingly submit sensitive prompts, secrets, or personal data to third-party services, creating privacy and compliance risk rather than code-execution risk.

Description-Behavior Mismatch

Medium
Confidence
84% confidence
Finding
The manifest frames the skill as a Bailian/DashScope integration for media capabilities. In addition to calling the TTS API, this file plays synthesized audio locally via `ffplay` and can write WAV output to an arbitrary path, which are extra host-interaction behaviors not conveyed by the description.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The manifest describes a skill for calling Aliyun Bailian via DashScope to support OCR, TTS, and image generation/transformation. This script goes beyond generating speech by invoking the local `ffplay` executable to play audio on the host, which is a host-side execution capability rather than a direct Bailian API integration requirement.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
tmp.write(wav_bytes)
        tmp.flush()
        try:
            subprocess.run(["ffplay", "-nodisp", "-autoexit", tmp.name], check=True)
        except FileNotFoundError:
            print("Error: ffplay not found. Please install ffmpeg.", file=sys.stderr)
            raise SystemExit(1)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
85% confidence
Finding
The natural-language instructions and sample prompts are Chinese-only, which can amount to a language policy issue when no user choice or rationale is provided. The file does not say the skill is intended only for a Chinese-speaking or region-specific audience, nor does it offer alternative language guidance.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The document presents the skill as a Chinese-language TTS feature and uses only Chinese text examples, but does not state that the tool is region-specific or that users may choose another language/locale. SQP-3 covers language or locale policy violations when a skill appears to force a specific language without user opt-in or justification.

Unpinned Dependencies

Low
Category
Supply Chain
Content
dashscope>=1.24.0
oss2>=2.19.1
requests>=2.31.0
pytest>=8.0.0
Confidence
92% confidence
Finding
The dependency is specified with only a lower bound, so future installs may resolve to different versions over time. This weakens build reproducibility and can unintentionally introduce vulnerable or breaking upstream releases into the skill.

Static analysis

No suspicious patterns detected.