Back to skill

Security audit

wecom-voice-agent

Security checks for vulnerabilities and agentic risk

Overview

This is a real WeCom voice assistant, but its public webhook can accept unauthenticated requests that spoof users and change local workflow records.

Review before installing. Do not expose the webhook publicly until WeCom signature/token/AES validation, replay protection, request size limits, and authorization checks are implemented. Keep it on localhost or behind an authenticated reverse proxy, disable supervisor webhooks unless TLS verification is restored and destinations are allowlisted, and treat the local ~/.wecom_voice and ticket databases as sensitive business data.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/wecom_webhook_server.py:155
Finding

Unauthenticated Public Webhook Allows Identity Spoofing and Unauthorized State Changes

Content
View full analysis

Vulnerability Details

File Location: scripts/wecom_webhook_server.py:155, 855-899, 1152-1238, 1651-1667, 1748
Vulnerability Type: Missing webhook authentication and authorization
Risk Level: High

Vulnerable Code

python
SERVER_HOST = "0.0.0.0"
python
def handle(self, callback):
    msgid = callback.get("msgid", "")
    msgtype = callback.get("msgtype", "")
    userid = callback.get("from", {}).get("userid", "")
    content = callback.get("voice", {}).get("content", "") or callback.get("text", {}).get("content", "")

    # ...
    chattype = callback.get("chattype") or callback.get("chat_type") or ""
    chatid = callback.get("chatid") or callback.get("chat_id") or ""
    if chattype == "group" or chatid:
        return self._handle_group(callback, chattype=chattype, chat_id=chatid)

    if msgtype == "voicemail":
        return self._handle_voicemail(callback)

    if msgtype == "voice":
        return self._handle_voice(callback)
    elif msgtype == "text":
        return self._handle_text(callback)
python
def _handle_group(self, callback, chattype="group", chat_id=""):
    msgtype = callback.get("msgtype", "")
    userid = callback.get("from", {}).get("userid", "")
    speaker_role = "admin" if userid in self.group_admins else "member"

    # ...
    if self.entity_extractor and self.todo_mgr:
        assign = self.entity_extractor.extract_assignment(content, userid)
        if assign.get("is_assignment") and assign.get("assignee"):
            return self._dispatch_group_assignment(assign, content)
python
ok = self.todo_mgr.register_todo(
    todo, assigner=assign["assigner"], assignee=assign["assignee"],
    chat_id=self._chat_ctx.get("chat_id", ""), source_group=True,
    auto_schedule=False,
)
python
def do_POST(self):
    parsed = urlparse(self.path)

    content_length = int(self.headers.get("Content-Length", 0))
    body = self.rfile.read(content_length)

    try:
        callback_data = json.loa
...[truncated 3222 chars]
Remediation
View remediation

Remediation Suggestions

  1. Authenticate every callback before parsing business data

    • Implement WeCom's documented signature verification using the configured callback token.
    • Validate the signature over the required timestamp, nonce, and encrypted payload fields.
    • Reject requests with missing, malformed, or invalid authentication data.
  2. Use authenticated callback encryption

    • Decrypt callbacks using the configured EncodingAESKey.
    • Validate the decrypted corporate/application identifier against the expected configured value.
    • Do not accept equivalent identity fields from an unauthenticated plaintext body.
  3. Prevent replay attacks

    • Enforce a narrow timestamp tolerance.
    • Persist or cache recently accepted nonce/signature/message-ID tuples.
    • Reject duplicate authenticated requests without periodically clearing the entire replay cache in a way that immediately re-enables old messages.
  4. Constrain network exposure

    • Bind to loopback by default.
    • Place the service behind an authenticated TLS reverse proxy when public exposure is necessary.
    • Restrict ingress to trusted proxy or WeCom source networks where operationally feasible.
  5. Enforce authorization independently of callback fields

    • Resolve user identity and role only after callback authentication.
    • Validate administrative actions against a server-side identity and role mapping.
    • Verify that task confirmation or rejection is performed by the authenticated assignee.
  6. Harden HTTP handling

    • Add a strict request-body size limit before reading Content-Length.
    • Apply rate limiting after authenticating the source.
    • Accept callbacks only on the intended path and reject unsupported content types.
    • Return generic errors without disclosing internal processing details.
  7. Add security tests

    • Test rejection of unsigned, incorrectly signed, stale, replayed, and improperly encrypted callbacks.
    • Test that sp ...[truncated 101 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (119)

Tainted flow: 'req' from os.environ.get (line 277, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The code sends notifications to a URL taken directly from the SUPERVISOR_WEBHOOK environment variable, creating an outbound data path controlled by deployment configuration. This can enable exfiltration of ticket metadata and user identifiers to an attacker-controlled endpoint, and the risk is amplified because TLS verification is explicitly disabled in the same function.

Content

Scanner excerpt · scripts/emotion_ticket_bridge.py (reported line 278)May include surrounding context.

python
payload = json.dumps({"text": message}).encode("utf-8")
            req = Request(SUPERVISOR_WEBHOOK, data=payload, headers={"Content-Type": "application/json"})
            resp = urlopen(req, timeout=5, context=ctx)
            return resp.status == 200
        except Exception as e:
            logger.warning(f"webhook 通知失败: {e}")

Tainted flow: 'req' from os.environ.get (line 313, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
92% confidence
Finding

The code allows the outbound clone API destination to be fully overridden by the CLONE_API_URL environment variable, then sends an Authorization bearer token plus authorization metadata to that URL via urlopen. If an attacker or untrusted deployment config controls the environment, this becomes an SSRF/exfiltration path that can leak API credentials and sensitive voice-authorization data to an arbitrary host.

Content

Scanner excerpt · scripts/voice_clone.py (reported line 317)May include surrounding context.

python
headers={"Content-Type": "application/json",
                                   "Authorization": f"Bearer {CLONE_API_KEY}"})
            ctx = ssl.create_default_context()
            with urlopen(req, timeout=15, context=ctx) as resp:
                body = json.loads(resp.read().decode("utf-8"))

            # 兼容常见响应字段

Tainted flow: 'req' from os.environ.get (line 469, credential/environment) → urllib.request.urlopen (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/voice_policy.py (reported line 476)May include surrounding context.

python
"Authorization": f"bearer; {api_key}"},
            )
            ctx = ssl.create_default_context()
            with urlopen(req, timeout=10, context=ctx) as resp:
                body = resp.read()
            os.makedirs(os.path.dirname(output_path), exist_ok=True)
            with open(output_path, "wb") as f:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
| v2.4.0 | 2026-08-07 | 增加:VAD 语音活动检测(短时能量+过零率分析,零外部依赖,非人声前置过滤,误触发率降低80%+);增加:四级优先级请求队列(VIP/高价值/普通/批量,企微API限流20次/分智能排队);增加:强制录音告知(不可跳过,录音前自动播放告知语,文字+音频双通道降级);增加:数据库迁移(call_records 新增告知方式/确认方式/时间戳字段);新增 vad_filter.py、priority_queue.py 脚本;升级 compliance.py 至 v3.0(强制录音告知系统);扩展 wecom_webhook_server.py VAD 前置过滤+优先级路由 |
| v2.3.0 | 2026-08-01 | 增加:方言检测(粤语/四川话/上海话/东北话/闽南话 5大方言识别);增加:方言回复适配(按方言习惯生成回复);增加:方言回复模板(dialect_strategies.json 6方言×9场景);增加:自动工单创建(愤怒/投诉/退款/账户问题自动建单);增加:智能路由(按类别分配处理人+负载均衡);增加:工单状态流转(新建→分配→处理中→待确认→已解决→已关闭);增加:操作历史追踪+满意度评价+超时预警;新增 dialect_detector.py、ticket_manager.py 脚本;新增 dialect_strategies.json 模板;扩展 session_manager.py 方言+工单字段;扩展 wecom_webhook_server.py 方言+工单集成 |
| v2.2.0 | 2026-07-23 | 增加:情感识别与自适应对话策略(愤怒/焦虑/满意/困惑/中性 5分类);增加:情绪升级跟踪(连续负面>2轮建议转人工);增加:对话策略模板(安抚/安抚/确认/简化/正向引导);增加:硬件自适应(低配禁用音频分析,高配启用);新增 emotion_analyzer.py 脚本、emotion_strategies.json 策略模板;扩展 session_manager.py 情感状态跟踪 |
| v2.1.0 | 2026-07-15 | 修复bug:安全审计修复(移除测试代码中的 /etc/passwd 路径引用,改为安全测试字符串) |
| v2.0.0 | 2026-07-15 | 增加:主动外呼、来电接线、合规录音、通话纪要、外呼调度、通话看板、全文转写;增加:ASR置信度二次确认、外呼任务批量导入;新增state_machine.py、compliance.py、ivr_minutes.py、scheduler.py、stats.py、transcriber.py六个脚本 |
| v1.3.0 | 2026-07-10 | 增加:wttr.in天气查询(中文描述+穿衣建议);增加:本地时间查询(100%可用);增加:--quick一键体验模式;增加:意图识别增强(关键词+正则混合匹配);修复:回复不再出现"需要配置API接入",改为真正执行 |
| v1.2.0 | 2026-07-09 | 增加:wecom_webhook_server.py企业微信回调服务器;增加:step_by_step_setup.md分步部署指南;增加:多消息类型支持(文本/语音/图片/文件/视频) |

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · references/step_by_step_setup.md (reported line 176)May include surrounding context.

当你的技能需要回发消息、查询日历时,需要获取 access_token:

bash
curl -s -X POST \
  'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ww1234567890abcdef&corpsecret=YOUR_SECRET' \
  | python -c "import sys,json;d=json.load(sys.stdin);print(d['access_token'])"

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The bridge automatically creates tickets, raises priority, and notifies supervisors based on inferred user emotion without any confirmation or user-facing notice. This can cause unauthorized workflow actions, privacy surprises, and abuse through intentionally crafted text that triggers escalations.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The webhook sender disables both certificate validation and hostname verification, which defeats TLS protections and allows man-in-the-middle interception or redirection. That means supervisor notifications containing ticket details, user IDs, and sentiment context can be read or altered by an attacker on the network path.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
94% confidence
Finding

The webhook notification transmits user and ticket information externally without any visible warning, consent, or minimization. In this skill context, the message includes user ID, ticket title, time, and emotional assessment, which may constitute sensitive operational or personal data.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The filter is intended to block noise/silence before downstream voice handling, but _error_result() and the convenience wrappers default to is_speech=True on errors or malformed inputs. An attacker can provide invalid, unsupported, or intentionally corrupted audio to bypass this gate and force downstream processing, defeating the protection boundary and potentially enabling resource abuse or unsafe voice-triggered actions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The help text states the assistant is private-chat only, but the code explicitly processes group chats, responds to mentions, and supports group task assignment and confirmation flows. This can mislead users and administrators about where data is processed, causing sensitive information to be shared or acted on in group contexts without informed expectation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README instructs users to expose a local webhook server to the public internet via ngrok/frpc but does not warn about authentication, source validation, rate limiting, or the risk of publishing a local service. In this skill context, that omission is more dangerous because the exposed endpoint appears to process enterprise chat/voice callbacks and may handle sensitive data or trigger actions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

The README simultaneously says proactive outbound calling is unsupported and also advertises v2.0 support for that capability. Security-relevant capability confusion is dangerous because reviewers and deployers may underestimate the system's reach, permissions, telecom exposure, and consent requirements, leading to unsafe deployment decisions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The README makes contradictory privacy claims: it says the skill does not store or upload user voice data, but later states call recordings are stored locally under a persistent directory. This is dangerous because operators and end users may rely on the stronger privacy claim and enable the feature without informed consent, creating compliance, privacy, and data-retention risk.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill documentation exposes broad capabilities including environment access, file read/write, MCP, network, and shell execution, but it does not declare any explicit tool scope or permission boundaries. In an agent ecosystem, missing least-privilege constraints makes it easier for the skill to invoke powerful operations unexpectedly, increasing the blast radius of prompt injection, misconfiguration, or future code changes.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The markdown explicitly requires the assistant to apologize to users in Chinese during severe errors. This imposes a specific language policy on all users without any opt-in or documented region-specific justification, which matches the locale/language policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The FAQ says all error prompts have been converted to Chinese, with no indication that users can choose another language. This is a natural-language policy issue because it enforces one language by default rather than supporting user preference or documenting a justified locale restriction.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

yaml
intents:
  query_order:
    endpoint: "https://api.example.com/orders"
    method: "GET"
    auth_env: "ORDER_API_TOKEN"
    timeout: 10

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

This duplicate finding refers to the same documented session persistence behavior: creating sessions by userid and retrieving session details. In the context of a voice assistant handling potentially sensitive work conversations, durable session state can expose personal or operational data if accessed by other local users, backup systems, or misconfigured tooling.

Content

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

bash
# 创建新会话
python D:/skill/wecom-voice-agent/scripts/session_manager.py create --userid zhangsan

# 查询会话状态(表格格式)
python D:/skill/wecom-voice-agent/scripts/session_manager.py get --session_id xxx --format table

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

This duplicate finding refers to the same documented session persistence behavior: creating sessions by userid and retrieving session details. In the context of a voice assistant handling potentially sensitive work conversations, durable session state can expose personal or operational data if accessed by other local users, backup systems, or misconfigured tooling.

Content

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

bash
# 创建新会话
python D:/skill/wecom-voice-agent/scripts/session_manager.py create --userid zhangsan

# 查询会话状态(表格格式)
python D:/skill/wecom-voice-agent/scripts/session_manager.py get --session_id xxx --format table

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This manifest defines several intents and backend actions, but it does not specify the trigger phrases, activation constraints, or negative examples that determine when each intent should be invoked. Without explicit trigger scope, similar everyday requests like order, account, or reimbursement questions could overlap and cause unintended skill activation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

All user-facing response and fallback text in the configuration is written in Chinese, and the file provides no indication that language is selectable or user-driven. This can violate language or locale policy when a skill implicitly forces a specific language without opt-in or documented regional justification.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The knowledge-query intent uses extremely common phrases such as '怎么', '如何', '为什么', '在哪里', and '什么时候', which are likely to appear in ordinary conversation. In a voice assistant, this can cause accidental routing into a generic knowledge handler, potentially bypassing safer, more specific intent handling and producing unintended answers or actions downstream.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The message-sending intent is action-bearing and includes broad triggers like '告诉', '通知', and pattern-like phrases such as '给.*发' and '跟.*说'. In a voice setting, ambiguous activation of a send-message action can lead to unintended outbound communications, privacy leakage, or social engineering amplification if the wrong recipient or content is inferred.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The exit intent includes common conversational phrases such as '谢谢', '好吧', and '没事了', which are often spoken during normal interaction without intent to terminate the session. This creates a high risk of accidental session shutdown, which can interrupt workflows and cause users to lose context or abandon partially completed actions.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The transfer-to-human intent contains highly generic terms like '人工', '客服', and '真人', which may appear in ordinary support-related questions rather than as escalation requests. This can cause unnecessary handoffs, denial of self-service, or operational abuse by routing users to human agents when they did not clearly request it.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.insecure_tls_verification

HTTPS certificate verification is disabled.

Warn
Code
suspicious.insecure_tls_verification
Location
scripts/emotion_ticket_bridge.py:273