Back to skill

Security audit

Giggle Generation Speech

Security checks for vulnerabilities and agentic risk

Overview

This speech-generation skill mostly does what it claims, but it stores full user prompts locally and forwards unvalidated service output in ways users should review before installing.

Install only if you are comfortable sending text to giggle.pro and storing submitted prompts on the local machine. Avoid using it for secrets or sensitive text until prompt retention, task-id validation, URL validation, and cleanup behavior are tightened.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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

Warning
Location
scripts/text_to_audio_api.py:34
Finding

Unsanitized Task IDs Permit Filesystem Path Traversal

Content
View full analysis
bool: return (_get_log_dir() / f"{task_id}.sent").exists() def _mark_sent(task_id: str) -> None: (_get_log_dir() / f"{task_id}.sent").touch() def _save_task_text(task_id: str, text: str) -> None: try: (_get_log_dir() / f"{task_id}.prompt").write_text(text, encoding='utf-8') except Exception: pass def _load_task_text(task_id: str, truncate: bool = True) -> Optional[str]: try: f = _get_log_dir() / f"{task_id}.prompt" if f.exists(): text = f.read_text(encoding='utf-8').strip() if truncate: return text[:20] + "..." if len(text) > 20 else text return text except Exception: pass return None def _increment_query_count(task_id: str) -> int: f = _get_log_dir() / f"{task_id}.count" count = _get_query_count(task_id) + 1 f.write_text(str(count)) return count ``` The task identifier is obtained either from a command-line argument or from a remote API response: ```python if args.query: if not args.task_id: print("Error: query mode requires --task-id", file=sys.stderr) sys.exit(1) count = _increment_query_count(args.task_id) ``` ```python task_id = result.get("data", {}).get("task_id") submitted_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') _save_task_text(task_id, args.text) ``` ### Technical Analysis The application interpolates `task_id` directly into filenames without validating its character set, length, path separators, or resolved location. `pathlib.Path` does not automatically confine a joined path to its intended parent directory. A value containing traversal components such as `../../target` can therefore cause the resulting ...[truncated 1806 chars]
Remediation
View remediation
str: if not isinstance(task_id, str) or not TASK_ID_PATTERN.fullmatch(task_id): raise ValueError("Invalid task ID") return task_id ``` 2. Apply validation to both command-line and API-provided task IDs. 3. Resolve each constructed path and verify that it remains under the resolved log directory: ```python def task_file(task_id: str, suffix: str) -> Path: task_id = validate_task_id(task_id) base = _get_log_dir().resolve() candidate = (base / f"{task_id}{suffix}").resolve() if candidate.parent != base: raise ValueError("Task path escapes the state directory") return candidate ``` 4. Replace all direct path interpolation with the centralized `task_file` helper. 5. Reject missing or non-string API task IDs before saving task state. 6. Do not silently suppress filesystem exceptions; report them safely so validation and storage failures are observable. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/text_to_audio_api.py:28
Finding

Full User Prompts Are Persisted Without Adequate Protection or Cleanup

Content
View full analysis
Path: log_dir = Path.home() / '.openclaw' / 'skills' / 'giggle-generation-speech' / 'logs' log_dir.mkdir(parents=True, exist_ok=True) return log_dir ``` ```python def _save_task_text(task_id: str, text: str) -> None: try: (_get_log_dir() / f"{task_id}.prompt").write_text(text, encoding='utf-8') except Exception: pass ``` ```python task_id = result.get("data", {}).get("task_id") submitted_at = datetime.now().strftime('%Y-%m-%d %H:%M:%S') _save_task_text(task_id, args.text) ``` The documentation describes the writes only as task state used for Cron deduplication: ```markdown 1. **Write** to `~/.openclaw/skills/giggle-generation-speech/logs/` – Task state files for Cron deduplication ``` ### Technical Analysis Every submitted synthesis prompt is written in full to a persistent `.prompt` file. These files are used only to display a short preview in later status messages, but the implementation stores the complete source text. The implementation does not: - Set an explicit restrictive mode on the state directory or prompt files. - Delete prompt data after completion, failure, or timeout. - Establish a retention period. - Obtain explicit consent for persistent prompt storage. - Clearly disclose that the complete user text is stored locally. Filesystem accessibility consequently depends on the process umask and surrounding account configuration. The indefinite retention of complete prompts exceeds what is required to support a short status preview and Cron deduplication. ### Attack Path 1. A user submits text that may contain confidential, personal, commercial, or otherwise sensitive information. 2. The Skill sends the text ...[truncated 846 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Note
Location
scripts/text_to_audio_api.py:171
Finding

Untrusted Remote API Content Is Forwarded Without Validation

Content
View full analysis
str: data = task_result.get("data", {}) urls = data.get("urls", []) if not urls: return "" url = urls[0] # Online playback: remove attachment and encode tilde view_url = url.replace("&response-content-disposition=attachment", "") view_url = view_url.replace("?response-content-disposition=attachment&", "?") view_url = view_url.replace("?response-content-disposition=attachment", "") view_url = view_url.replace("~", "%7E") return view_url ``` Remote error text and URLs are embedded directly into output: ```python if status == TaskStatus.COMPLETED: if _check_sent(args.task_id): sys.exit(0) _mark_sent(args.task_id) url = client.extract_audio_url(result) text_preview = _load_task_text(args.task_id) or "speech" print(f"Speech is ready.\n\nThe voice-over for “{text_preview}” is complete.\n\n[Listen]({url})") sys.exit(0) elif status == TaskStatus.FAILED: text_preview = _load_task_text(args.task_id) or "speech" err = data.get("err_msg", "Unknown error") print(f"Speech generation encountered a problem.\n\n“{text_preview}” could not be completed: {err}") sys.exit(0) ``` The Skill instructions require raw forwarding behavior: ```markdown 3. **Forward raw stdout** – Script output (audio links, status) is passed to the user as-is ``` ```markdown | Non-empty plain text (not starting with `{`) | **Forward to user as-is**, **remove Cron** | ``` ### Technical Analysis The query response from the remote service is treated as trusted presentation content. The application in ...[truncated 2091 chars]
Remediation
View remediation
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (19)

Tainted flow: 'url' from os.getenv (line 327, credential/environment) → requests.post (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 · scripts/text_to_audio_api.py (reported line 111)May include surrounding context.

python
url = f"{self.BASE_URL}{self.SUBMIT_ENDPOINT}"
        try:
            response = requests.post(url, headers=self.headers, json=payload, timeout=30)
            response.raise_for_status()
            result = response.json()
            if result.get("code") != 200:

Tainted flow: 'url' from os.getenv (line 327, 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 · scripts/text_to_audio_api.py (reported line 125)May include surrounding context.

python
url = f"{self.BASE_URL}{self.QUERY_ENDPOINT}"
        params = {"task_id": task_id}
        try:
            response = requests.get(url, headers=self.headers, params=params, timeout=30)
            response.raise_for_status()
            result = response.json()
            if result.get("code") != 200:

Tainted flow: 'url' from os.getenv (line 327, 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 · scripts/text_to_audio_api.py (reported line 138)May include surrounding context.

python
"""获取可用音色列表"""
        url = f"{self.BASE_URL}{self.PRESET_TONES_ENDPOINT}"
        try:
            response = requests.get(url, headers=self.headers, timeout=30)
            response.raise_for_status()
            result = response.json()
            if result.get("code") != 200:

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
python3 scripts/text_to_audio_api.py --list-voices

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
97% confidence
Finding

The skill explicitly instructs the agent to return full signed audio URLs containing Policy, Key-Pair-Id, and Signature query parameters directly to the user. Those are bearer-style access tokens; exposing them in raw form can leak temporary authorized access to private assets, and if logs, memories, transcripts, or downstream tools capture them, third parties may reuse them until expiry.

Content

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

md
---

## Link Return Rule

Audio links returned to the user must be **full signed URLs** (with Policy, Key-Pair-Id, Signature query params). Correct: `https://assets.giggle.pro/...?Policy=...&Key-Pair-Id=...&Signature=...`. Wrong: do not return unsigned URLs with only the base path (no query params). The script handles `~` encoding to `%7E`; keep as-is when forwarding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill clearly requires environment access, file writes, and network access, and also instructs use of cron and exec, but it does not declare an explicit tool scope such as permissions or allowed-tools. This creates an authorization and review gap: a host may grant broader capabilities than intended, making the skill harder to safely sandbox and audit.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger list includes broad everyday phrases such as '朗读这段文字', '把这段文字读出来', and '我需要一段配音', which can cause the skill to activate in situations where the user did not explicitly intend to invoke this external TTS workflow. Because activation leads to outbound API use, file writes, cron registration, and direct stdout forwarding, accidental triggering can expose user text to a third-party service and create unintended side effects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

Natural-language strings throughout the script, including help text, status messages, and errors, are presented only in Chinese. This imposes a specific language/locale on users without opt-in or justification, which matches the policy's language-choice concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

User-provided text is silently written to local log-like files without notice or consent. This is dangerous because TTS input often contains personal, proprietary, or otherwise sensitive material, and undisclosed plaintext retention creates avoidable exposure on the local system.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The script transmits user-supplied text to an external third-party service for speech generation. This is expected for a cloud TTS integration, but it is still a real privacy/security concern because sensitive user content leaves the local environment and is processed by an outside provider; the skill context makes this more acceptable functionally, but disclosure and consent are still important.

Content

Scanner excerpt · scripts/text_to_audio_api.py (reported line 111)May include surrounding context.

python
url = f"{self.BASE_URL}{self.SUBMIT_ENDPOINT}"
        try:
            response = requests.post(url, headers=self.headers, json=payload, timeout=30)
            response.raise_for_status()
            result = response.json()
            if result.get("code") != 200:

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified as requests>=2.31.0, which allows installation of any later version and does not guarantee reproducible builds. This creates supply-chain risk because different environments may resolve to different releases, including unexpectedly vulnerable or behavior-changing versions.

Content

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

text
# giggle-generation-speech 依赖包

requests>=2.31.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
88% confidence
Finding

Because requests is not pinned, it is impossible to verify from this manifest whether deployment will use a release affected by known advisories. In a skill that may make outbound API calls to a TTS service, unresolved dependency versions increase the chance of pulling a vulnerable client library version into production.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill persists task-tracking state under the user's home directory, which expands its behavior beyond simple speech generation and creates a local privacy footprint. While the state files themselves are not highly sensitive, they can reveal task IDs, usage patterns, and support later correlation with stored prompt content.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code writes the user's submitted text to a local .prompt file, which can expose potentially sensitive content spoken by the user to other local users, backups, or forensic recovery. In a TTS skill, users may submit private material for narration, so retaining plaintext prompts on disk increases privacy and confidentiality risk beyond the core function.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.