Back to skill

Security audit

ecommerce-voice-cs

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do the advertised voice customer-service and sales work, but it under-protects API keys and session state in ways users should review before installing.

Install only if you are comfortable sending generated customer-service or sales text to SenseAudio and retaining audio files locally. Avoid passing real API keys through chat-style payloads until the skill stops writing api_key values to .session_state, fixes session-id collision handling, restricts audio output to a controlled directory, and requires strict explicit confirmation before activating sales mode.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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

Error
Location
helper.py:118
Finding
SenseAudio API keys are persisted in plaintext session-state files<![CDATA[ ## Vulnerability Details **File Location**: `helper.py:118-123` and `helper.py:343-349` **Vulnerability Type**: Plaintext credential storage **Risk Level**: High ### Vulnerable Code ```python def _save_state(session_id: str, state: dict[str, Any]) -> None: _state_file(session_id).write_text( json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8", ) ``` Caller-provided API keys are copied into the state configuration: ```python @staticmethod def _merge_after_sales_payload(cfg: dict[str, Any], payload: dict[str, Any]) -> None: for key in ("api_key", "refund_policy", "shipping_fee_by", "audio_output_path"): value = str(payload.get(key, "")).strip() if value: cfg[key] = value ``` Equivalent storage occurs for sales-mode configuration through `_merge_sales_payload`. Repository artifacts confirm that the resulting state schema contains plaintext credentials: ```json "sales": { "api_key": "k", "voice_id": "male_0018_a" } ``` This pattern appears in: - `.session_state/persist-fix.json:13` - `.session_state/persist-fix-2.json:13` - `.session_state/persist-fix-3.json:13` ### Technical Analysis The Skill accepts a SenseAudio API key through its runtime payload, stores it in the session configuration, and serializes the complete configuration directly to a JSON file. There is no credential redaction, encryption, secret-store integration, expiry mechanism, or explicit owner-only file permission enforcement. Although the API key is legitimately required to authenticate TTS requests, persisting it in session state is not necessary for the declared functionality. The key can remain in process memory or be retrieved from the documented `SENSEAUDIO_API_KEY` environment variable when a request is made. The generated files inherit permissions from the process umask. In a shared or incorrectly configured environment, other local users or processes may therefore be able to read the credential ...[truncated 1231 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `api_key` from both `AFTER_SALES_DEFAULTS` and `SALES_DEFAULTS`. 2. Never merge credentials into objects that are passed to `_save_state`. 3. Resolve credentials only at request time from: - `SENSEAUDIO_API_KEY`; - a host-provided secret reference; or - an operating-system or cloud secret manager. 4. If per-session credentials must be supported, keep them in a short-lived in-memory credential cache and clear them when the session ends. 5. Add a serialization allowlist containing only non-sensitive fields rather than serializing the complete runtime state. 6. Create the state directory and files with owner-only permissions, such as directory mode `0700` and file mode `0600`, as defense in depth. 7. Add `.session_state/` to source-control and packaging exclusions. 8. Delete existing state artifacts and rotate any genuine credentials that may previously have been written there. 9. Add automated tests asserting that serialized state never contains fields named `api_key`, `token`, `secret`, or `password`. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
helper.py:101
Finding
Lossy session-ID normalization enables cross-session state collisions<![CDATA[ ## Vulnerability Details **File Location**: `helper.py:101-115` **Vulnerability Type**: Session-state isolation failure **Risk Level**: High ### Vulnerable Code ```python def _state_file(session_id: str) -> Path: safe = "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in session_id) return STATE_DIR / f"{safe}.json" def _load_state(session_id: str) -> dict[str, Any]: path = _state_file(session_id) if not path.exists(): return { "mode": "idle", "stage": "idle", "after_sales": dict(AFTER_SALES_DEFAULTS), "sales": dict(SALES_DEFAULTS), } return json.loads(path.read_text(encoding="utf-8")) ``` ### Technical Analysis The mapping from a caller-controlled `session_id` to a state filename is not one-to-one. Every character other than an alphanumeric character, hyphen, or underscore is replaced with an underscore. Different session IDs can consequently resolve to the same file. For example: - `tenant/a` becomes `tenant_a.json` - `tenant:a` becomes `tenant_a.json` - `tenant_a` becomes `tenant_a.json` The state file contains mode configuration, business rules, output paths, product information, and potentially the plaintext API key described in the preceding finding. No authenticated principal, tenant identifier, or original session ID is recorded and checked before the state is loaded or overwritten. This is an access-control problem rather than ordinary filename sanitization. Preventing path traversal is appropriate, but the chosen transformation destroys session uniqueness. ### Attack Path 1. A victim uses a predictable session ID such as `tenant/a`. 2. The Skill normalizes that ID and stores the victim state in `.session_state/tenant_a.json`. 3. An attacker who can choose a session ID submits `tenant_a` or another colliding value such as `tenant:a`. 4. `_state_file` maps the attacker-controlled ID to the victim's state file. 5. `_load_state` loads the ...[truncated 1093 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Derive filenames from a collision-resistant encoding of the exact session ID, for example: ```python import hashlib digest = hashlib.sha256(session_id.encode("utf-8")).hexdigest() path = STATE_DIR / f"{digest}.json" ``` 2. Bind each session to an authenticated host principal or tenant. Do not treat a caller-supplied session ID as proof of authorization. 3. Store the exact original session ID and authenticated owner inside the state record. 4. On every load and save, verify that both the session ID and owner match the current request. 5. Use a database or host session store with unique constraints and tenant-aware access controls instead of unauthenticated flat files where possible. 6. Generate high-entropy session identifiers server-side and prevent untrusted callers from selecting arbitrary identifiers. 7. Add tests using known collisions such as `tenant/a`, `tenant:a`, and `tenant_a`. 8. Migrate or invalidate existing state files because their ownership cannot be reliably inferred from normalized filenames. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
helper.py:261
Finding
Sales mode activates on any non-empty reply without explicit confirmation<![CDATA[ ## Vulnerability Details **File Location**: `helper.py:261-268` **Vulnerability Type**: Authorization and consent-state bypass **Risk Level**: Medium ### Vulnerable Code ```python if not self._is_confirm_message(message, payload): # In some hosts, Chinese confirmation text may be transcoded poorly. # For sales mode, treat any non-update, non-cancel reply here as confirmation. if message.strip(): state["stage"] = "active" _save_state(session_id, state) ``` ### Technical Analysis The declared Skill protocol requires sales mode to become active only after the user replies with the designated start phrase or otherwise explicitly confirms entry. The implementation initially checks `_is_confirm_message`, but then deliberately treats every other non-empty message as confirmation unless it is recognized as a configuration update or cancellation. As a result, unrelated text, a question, an accidental input, or a negative statement not matching the narrow cancellation patterns transitions the state to `active`. This defeats the confirmation boundary intended to precede external TTS processing. Once active, subsequent sales-mode input is incorporated into a generated sales script and sent to the external SenseAudio TTS endpoint. The network request itself is consistent with the declared cloud-TTS functionality, but triggering that behavior without explicit confirmation exceeds the intended authorization flow. ### Attack Path 1. A session enters sales mode and supplies all required configuration. 2. The state advances to `awaiting_confirmation`. 3. The user sends any unrelated non-empty message that is neither a recognized update nor a recognized cancellation. 4. `_is_confirm_message` returns false. 5. The fallback branch nevertheless sets `state["stage"]` to `active`. 6. The modified active state is persisted to disk. 7. The next message is converted into a sales script and transmitted to SenseAudio for TTS generation, consumin ...[truncated 718 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the fallback that treats arbitrary non-empty input as confirmation. 2. Transition to `active` only when `_is_confirm_message(message, payload)` returns true. 3. Prefer a typed host signal such as `confirm_enter=True` over language-dependent free-text matching. 4. If text confirmation is supported, use a strict allowlist of documented phrases and reject all other text while awaiting confirmation. 5. Treat unrecognized responses as non-confirming and return the confirmation prompt again. 6. Expand cancellation handling carefully, but do not infer consent from the absence of cancellation. 7. Log confirmation events without recording credentials or sensitive message content. 8. Add state-machine tests proving that questions, unrelated text, malformed text, and negative replies cannot activate sales mode. 9. Require a fresh explicit confirmation whenever material configuration fields are changed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
75% confidence
Finding
The code chunk demonstrates a single skill class named EcommerceVoiceCSSkill and configures it with after-sales/refund-policy parameters. Its example interactions are customer-service oriented ('act as a customer service robot', 'can I return it after opening?') and include TTS-related voice/audio settings. However, the declared description emphasizes two distinct, independently enabled modes, including a separate phone sales mode that generates promotional scripts from product features and discount ranges. That second mode, and any trigger-based switching between modes, is not evidenced in the supplied code chunk. The TTS/audio-output aspects do align, but the overall declared scope is broader than what the provided code actually demonstrates.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The description claims a broader, dual-purpose voice skill with two isolated modes and speech generation features. The code shown only contains an after-sales text response generator based on refund rules. That is partially consistent with one portion of the description, but materially incomplete relative to the declared primary purpose: there is no evidence of the second phone-sales mode or any audio/TTS-related behavior. Because the declared functionality centers on a two-mode voice skill and the actual code only implements one text-only rules component, this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
描述的核心卖点是“双模式电商语音技能”,其中售后客服和电话推销应分别可触发、独立运行。但该代码块只围绕售后客服流程实现:切换到客服模式、收集退款规则配置、生成客服回复、输出合成语音。没有任何电话推销模式的状态、触发词、配置项、产品信息处理或销售话术生成逻辑。因此这不是轻微遗漏,而是对主要能力范围的实质性少实现,描述明显高于代码实际行为。其余如 API key、voice clone、音频落盘等与已实现的客服语音能力一致。

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
代码的核心功能只是底层语音合成服务封装,而不是描述中的完整“电商 AI 语音技能”。它确实支持将给定文本合成为音频并保存文件,也支持 API Key/音色句柄等配置校验,因此与“输出 TTS 音频、配置确认、音色选择、语音落盘”部分有一定重合。但声明中的主要业务能力——依据退款规则生成售后客服回复、依据产品信息生成电话推销话术、通过不同触发词切换两套互不干扰模式——在代码中完全没有体现。相反,代码包含了对 SenseAudio 云端 API 的 HTTP 访问,这属于实际资源访问能力,但声明权限为空且未明确说明存在外部网络调用。因此描述与代码实际行为存在实质性不一致。

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The document specifies that generated audio files are retained on disk and that the host should immediately autoplay them, but it does not require user consent, retention limits, or safety checks on playback behavior. In a voice-enabled ecommerce/customer-service skill, this can expose sensitive spoken content to unintended listeners and leave recoverable artifacts on disk, increasing privacy and local data exposure risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares capabilities that imply environment access, local file read/write, and network use, but it does not define any explicit tool scope or permission boundaries. In a skill host, this can lead to overbroad authority being granted implicitly, making accidental secret exposure, arbitrary file writes, or unintended outbound requests more likely.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The description and trigger design define the skill as a Chinese-language voice/customer-service workflow, but the document does not offer language/locale choice or state that the skill is intentionally limited to a Chinese-region use case. Under the policy, forcing a specific language without opt-in is a natural-language policy violation.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly instructs the host to save generated audio to a local path and retain it after playback, but the description does not present this as a user-visible privacy or storage warning. Because generated speech may contain customer-service content, product details, or other sensitive conversation data, silent retention can create local data exposure, accidental disclosure, and accumulation of sensitive files.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The code hard-codes a child-like default voice (`child_0001_b`) and uses it automatically unless an environment variable overrides it. In a customer-service and telemarketing voice skill, this can mislead recipients, create compliance and consent issues, and increase the risk of socially manipulative or inappropriate synthetic voice use without explicit operator choice or disclosure.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The activation triggers, confirmation phrases, and user-facing responses are fixed in Chinese, and the code even special-cases Chinese confirmation handling. This enforces a specific language/locale without offering the user a choice or documenting an opt-in, which matches the language-policy violation category.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill accepts api_key=... from user messages, merges it into session state, and persists that state to a JSON file under .session_state. This can store sensitive credentials on disk without clear warning, increasing risk of credential disclosure through local file access, backups, logs, or multi-user host environments.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
After-sales mode sends user-provided content and configuration to EcommerceVoiceCSSkill/underlying TTS functionality using an API key, but the helper layer does not clearly disclose that this content is transmitted to an external service. In a customer-service workflow, messages may contain personal order details or other sensitive information, so silent off-box transmission creates a real privacy and data-handling risk.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
In sales mode, any non-empty reply during the confirmation stage is treated as confirmation, despite the surrounding UX text implying an explicit confirmation step. This weakens the safety boundary around mode activation and can cause accidental entry into an active mode that sends content to external TTS services and writes files to disk.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Sales mode synthesizes generated content through VoiceCloneService using an external API, but the user is not clearly informed that content is being sent to a third-party service. Although sales text is less sensitive than after-sales support data, undisclosed external transmission still poses privacy, compliance, and trust risks.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The file returns user-facing service replies entirely in Chinese, including the default prompt and all after-sales guidance. This forces a specific language for all users without any opt-in, fallback, or indication that the skill is intentionally limited to a Chinese-speaking locale.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest says the skill supports two separately triggered capabilities: after-sales customer service and telephone sales script generation. In this file, all mode logic, trigger handling, prompts, configuration, and response generation are dedicated to customer service mode, and there is no implementation of a second telemarketing mode, product-pitch generation, or separate trigger phrase for that advertised capability.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The description promises two capabilities that can be independently enabled by different trigger words and kept isolated from each other. The code defines a single TRIGGER_PHRASE and uses a single boolean session flag is_cs_mode, which supports only entering customer service mode and does not implement an independent second mode or isolation between two modes.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The trigger phrase is fixed to Chinese and the interaction flow relies on Chinese-only text responses, including the activation instruction at L62. This imposes a specific language/locale without any explicit user choice, which matches the language-policy violation criteria.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill uploads a voice sample and later generates audio files without any consent notice, retention disclosure, or confirmation in this file. In a voice-cloning context, silent collection and processing of biometric-like voice data increases privacy risk and can enable unauthorized cloning or deceptive content generation if the host app does not add its own safeguards.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The skill accepts and stores an API key through `set_api_key`, and also hydrates `api_key` from runtime payload at L98-L100, but there is no user-facing notice, logging, or explanatory comment about credential handling. Accessing and using credentials is a covered operation for this rule when no disclosure is present.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill accepts runtime-supplied clone_api_url and api_key values and applies them directly, allowing a caller to redirect voice-sample uploads and synthesis requests to an arbitrary endpoint. In this skill's context, that is more dangerous because it processes sensitive voice data; a malicious or compromised host/plugin could silently exfiltrate credentials and biometric audio to attacker-controlled infrastructure.

External Transmission

Medium
Category
Data Exfiltration
Content
API_DOCS_URL = "https://senseaudio.cn/docs/api-key"
    TTS_DOCS_URL = "https://senseaudio.cn/docs/text_to_speech_api"
    TTS_API_URL = "https://api.senseaudio.cn/v1/t2a_v2"
    MODEL_NAME = "SenseAudio-TTS-1.0"
    SUPPORTED_AUDIO_FORMATS = {"mp3", "wav", "pcm", "flac"}
    SUPPORTED_SAMPLE_RATES = {8000, 16000, 22050, 24000, 32000, 44100}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The error/help text is presented only in Chinese, and similar Chinese-only messages appear elsewhere in the file. This imposes a specific language on users without an explicit opt-in or documented locale constraint, which matches the language/locale policy violation criteria.

Context-Inappropriate Capability

Medium
Confidence
85% confidence
Finding
The manifest describes selecting voices and outputting TTS audio for two business modes, but this class also exposes an upload_sample entry point for voice cloning via a private clone endpoint. Even though the method is not implemented, supporting sample-based cloning is a distinct capability beyond ordinary voice selection and is not clearly within the declared scope.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The code sends TTS content to a third-party remote service, but the skill description does not clearly disclose this external dependency or the associated data flow. In a customer-service/telemarketing context, the text may contain customer messages, refund details, or sales content, creating privacy and compliance risk if operators assume processing is local.

Static analysis

No suspicious patterns detected.