Back to skill

Security audit

sjht doubao text to image

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to be a real Doubao image generator, but it needs review because it handles an API key, sends prompts to an external service, and writes a preview page with a real HTML-injection flaw.

Install only if you are comfortable sending image prompts to Volcengine/Doubao and using an ARK API key. Prefer ARK_API_KEY from a protected environment over --api-key, avoid secrets or personal data in prompts, be cautious opening the generated index.html for prompts you did not fully control, and consider pinning dependencies before routine 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

Warning
Location
scripts/gen.py:61
Finding
Stored HTML Injection in the Generated Gallery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 61-76 and 101-102 **Vulnerability Type**: Stored HTML injection caused by incomplete contextual escaping **Risk Level**: Medium ### Complete Code Snippet ```python def _write_gallery(out_dir: Path, items: list[dict]) -> Path: """生成图库预览 HTML""" html_items = "" for it in items: fname = it["file"] prompt_escaped = it["prompt"].replace("<", "&lt;").replace(">", "&gt;") html_items += f""" <div class="card"> <a href="{fname}" target="_blank"> <img src="{fname}" alt="{prompt_escaped}" loading="lazy"> </a> <div class="meta"> <div class="prompt">{prompt_escaped}</div> <div class="info">{it.get('model','')} · {it.get('size','')} · {it.get('index','')}</div> </div> </div>""" ``` The accumulated markup is subsequently written directly to the gallery: ```python index_path = out_dir / "index.html" index_path.write_text(html, encoding="utf-8") ``` ### Technical Analysis The application attempts to sanitize the prompt by replacing only `<` and `>`. This is insufficient for an HTML attribute context because quotation marks and ampersands are not escaped. The prompt is placed inside the double-quoted `alt` attribute of an `<img>` element, so an attacker-controlled quotation mark can terminate the attribute and introduce new attributes such as `onload`. The user-controlled `--model` value is also inserted directly into an HTML element without any escaping. If a supplied model string is accepted through the generation workflow, it can introduce arbitrary HTML elements or event handlers. This is a stored injection issue: the malicious input is persisted in `index.html` and becomes active when the generated gallery is opened in a browser. ### Attack Path 1. An attacker supplies or influences the image prompt passed to the Skill. 2. The prompt contains an attribute-break ...[truncated 1612 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Apply contextual HTML escaping to every dynamic value, including quotation marks: ```python import html fname_escaped = html.escape(it["file"], quote=True) prompt_escaped = html.escape(it["prompt"], quote=True) model_escaped = html.escape(it.get("model", ""), quote=True) size_escaped = html.escape(it.get("size", ""), quote=True) index_escaped = html.escape(it.get("index", ""), quote=True) ``` 2. Use the escaped filename in both `href` and `src`, and use the other escaped values only in their intended contexts. 3. Prefer a template engine with automatic escaping rather than constructing HTML through f-strings. 4. Validate `--model` against an explicit allowlist of supported model identifiers. 5. Add regression tests using prompts containing `"`, `'`, `&`, `<`, `>`, and event-handler payloads. 6. Consider adding a restrictive Content Security Policy to the generated page, such as one that disallows inline scripts and external network connections. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/gen.py:213
Finding
API Key Exposure Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen.py`, lines 213 and 232-238; documented in `SKILL.md`, lines 20-41 **Vulnerability Type**: Sensitive credential exposure through process arguments, shell history, or execution logs **Risk Level**: Low ### Complete Code Snippet The command-line interface accepts the secret directly: ```python parser.add_argument("--api-key", "-k", default=None, help="ARK API Key") ``` The credential-loading logic prioritizes that command-line value: ```python def _load_api_key(cli_key: str | None) -> str: """按优先级读取 API Key:CLI 参数 > 环境变量 > ~/.doubao-image-gen/.env""" if cli_key: return cli_key if key := os.environ.get("ARK_API_KEY"): return key env_file = Path.home() / ".doubao-image-gen" / ".env" if env_file.exists(): for line in env_file.read_text(encoding="utf-8").splitlines(): line = line.strip() if line.startswith("ARK_API_KEY="): return line.split("=", 1)[1].strip().strip('"').strip("'") return "" ``` The documentation repeatedly encourages passing the key on the command line: ```bash python {baseDir}/scripts/gen.py --prompt "赛博朋克风格的上海夜景" --api-key YOUR_KEY python {baseDir}/scripts/gen.py --prompt "水墨风格的山水画" --count 4 --api-key YOUR_KEY python {baseDir}/scripts/gen.py --prompt "星空下的草原" --size 2K --api-key YOUR_KEY python {baseDir}/scripts/gen.py --prompt "古风仙侠" --out-dir ./output --api-key YOUR_KEY ``` ### Technical Analysis Secrets supplied as command-line arguments can be exposed outside the Python process. Depending on the operating environment, complete command lines may be visible through: - Shell history files. - Process inspection tools and operating-system process metadata. - Agent or automation execution logs. - Terminal session recording. - Monitoring and diagnostic systems. The implementation does not print the key itself, but that does not protect the secret from command-line-level exposure. The ...[truncated 1420 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--api-key` or mark it as deprecated and unsafe for routine use. 2. Prefer `ARK_API_KEY` supplied through a protected runtime environment or secret manager. 3. If interactive use is required, obtain the key with `getpass.getpass()` so it is not echoed or stored in shell history. 4. Replace all documentation examples with commands that do not place credentials in arguments. 5. If the dedicated `.env` fallback remains: - Require or verify restrictive file permissions. - Warn when the file is readable by group or other users. - Document a secure permission setting such as `chmod 600 ~/.doubao-image-gen/.env`. 6. Encourage narrowly scoped keys, usage limits, regular rotation, and immediate revocation after suspected disclosure. 7. Ensure automation and Agent logs redact credential-bearing arguments during the deprecation period. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:15
Finding
Unbounded and Incompletely Declared Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 15; related runtime imports in `scripts/gen.py`, lines 148-152 and 168 **Vulnerability Type**: Unpinned dependency installation and incomplete dependency declaration **Risk Level**: Low ### Complete Code Snippet The setup instructions use an open-ended version constraint: ```markdown - Python 3.8+ - openai 库:`pip install "openai>=1.0"` ``` The implementation imports that package at runtime and recommends the same unbounded installation command: ```python try: from openai import OpenAI except ImportError: print("请先安装 openai 库:pip install 'openai>=1.0'", file=sys.stderr) sys.exit(1) ``` A second runtime dependency is imported without being included in the setup command: ```python import requests as req_lib r = req_lib.get(url, timeout=60) ``` ### Technical Analysis The constraint `openai>=1.0` permits any future release satisfying the lower bound. There is no upper bound, lockfile, hash verification, or reviewed dependency snapshot. Installation may therefore select code that did not exist when the Skill was audited. The script also depends directly on `requests`, but the primary setup instructions do not install or pin it. It may currently be present transitively or already installed in a user's environment, but relying on that behavior produces non-reproducible deployments. Python packages and their transitive dependencies execute code in the local environment during installation and runtime. Open-ended resolution increases exposure to compromised future releases, malicious transitive dependency changes, and incompatible updates. No evidence indicates that the currently named packages are malicious; the issue is the unsafe dependency-management practice. ### Attack Path 1. A user follows the setup instruction and runs: ```bash pip install "openai>=1.0" ``` 2. The package resolver selects the latest matching release and its current transitive dependencies. ...[truncated 932 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add an explicit dependency manifest containing both direct dependencies, for example `requirements.txt` or `pyproject.toml`. 2. Pin reviewed versions of `openai` and `requests`, including a compatible upper bound or exact versions. 3. Generate and commit a lockfile for reproducible dependency resolution. 4. Use hash-verified installations where practical, such as `pip install --require-hashes`. 5. Review and update pinned dependencies through a controlled process with security scanning and compatibility tests. 6. Keep direct and transitive dependency inventories available for audit, preferably through a software bill of materials. 7. Update `SKILL.md` and runtime error messages so they reference the reviewed dependency file rather than an open-ended installation command. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'network' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Credential Access

High
Category
Privilege Escalation
Content
def _load_api_key(cli_key: str | None) -> str:
    """按优先级读取 API Key:CLI 参数 > 环境变量 > ~/.doubao-image-gen/.env"""
    if cli_key:
        return cli_key
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def _load_api_key(cli_key: str | None) -> str:
    """按优先级读取 API Key:CLI 参数 > 环境变量 > ~/.doubao-image-gen/.env"""
    if cli_key:
        return cli_key
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
print("❌ 未找到 API Key,请通过以下方式之一提供:", file=sys.stderr)
        print("   1. --api-key YOUR_KEY", file=sys.stderr)
        print("   2. 环境变量 ARK_API_KEY=YOUR_KEY", file=sys.stderr)
        print("   3. ~/.doubao-image-gen/.env 文件写入 ARK_API_KEY=YOUR_KEY", file=sys.stderr)
        return 2

    # 输出目录
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill documentation explains how to configure and use an API key, but it does not warn users that their prompts and related generation parameters are transmitted to the external Volcengine/Doubao image-generation service. In this context, users may include confidential business information, personal data, or proprietary creative material in prompts, so the lack of disclosure creates a meaningful privacy and consent risk.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The activation examples include broad, everyday phrases like '帮我画一张' and '生成一张', which can cause the skill to trigger in contexts where the user did not explicitly intend to invoke this external image-generation workflow. Because the skill can call a script that sends prompts to a third-party API and writes output files, accidental activation increases the risk of unintended data disclosure, unexpected external API usage, and surprise costs.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The file's user-facing description, CLI help text, status messages, and generated HTML explicitly use Chinese, including `<html lang="zh">`, without offering a language selection or documenting that the skill is intended only for a Chinese-speaking or region-specific audience. This is a natural-language policy issue because it imposes a specific language/locale on all users without opt-in.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This markdown file states that the skill automatically downloads generated images, writes a prompts.json mapping file, and generates an index.html preview page. For markdown files, SQP-2 applies when the description omits warnings about behaviors that may affect user data or system integrity; here the changelog notes the writes but gives no caution that running the skill will create multiple files in the working/output location.

Static analysis

No suspicious patterns detected.