Back to skill

Security audit

微盛企微管家SCRM

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent SCRM purpose, but it needs Review because it handles live CRM data and credentials with broad proxy access, unsafe credential persistence, and weak safeguards around uploads and writes.

Review before installing. Only use this on a trusted single-user machine, treat the APP KEY like a password, do not provide APP KEY values from untrusted sources, avoid disabling TLS verification or changing the base URL, and require explicit confirmation before uploads or business write actions. Rotate the APP KEY if it may have appeared in logs or command output.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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 (9)

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:22
Finding

Mandatory promotional content hijacks agent responses

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scrm.py:80
Finding

Persistent shell command injection through the APP KEY

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/environment.py:106
Finding

Restored APP KEY is disclosed in command output

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/get_access_token.py:93
Finding

APP KEY and access token are cached in plaintext without enforced permissions

Content
View full analysis
None: """保存缓存。""" ensure_dir(self.cache_file.parent) try: with open(self.cache_file, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) except OSError: pass ``` ### Technical Analysis The token cache stores both the long-lived APP KEY and the current access token in plaintext. The file and parent directory are created using default process permissions governed by the ambient `umask`. The code does not explicitly require file mode `0600` or directory mode `0700`. The write is also non-atomic. A crash or concurrent access can leave a partially written credential file, while symlink or filesystem manipulation may redirect the write in an attacker-influenced installation directory. ### Attack Path 1. The user authenticates through the Skill. 2. `TokenManager` receives an access token from the remote service. 3. The APP KEY and access token are written to `.cache/access_token.json`. 4. Another local account or process reads the cache where filesystem permissions allow it. 5. The recovered credentials are reused to access SCRM services. ### Impact Assessment A local attacker can potentially impersonate the user to the SCRM platform. The accessible scope corresponds to the victim's assigned role and can include customer information, tags, conversations, opportunities, campaigns, and write operations. The APP KEY may remain useful after the short-lived access token expires. ]]>
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/api_client.py:15
Finding

Credentials can be redirected to arbitrary destinations and sent without TLS verification

Content
View full analysis
ssl.SSLContext: """获取 SSL 上下文,支持通过环境变量跳过验证。""" if os.getenv("SCRM_SKIP_SSL_VERIFY", "").lower() in ("1", "true", "yes"): return ssl._create_unverified_context() return ssl.create_default_context() def fetch_personal_access_token(app_key: str, *, base_url: str = DEFAULT_BASE_URL) -> dict[str, Any]: """通过 personal app_key 获取 Access Token 及当前用户身份。""" query = parse.urlencode({"app_key": app_key}) url = f"{base_url.rstrip('/')}/openapi/personal_access_token?{query}" ssl_context = _get_ssl_context() try: with request.urlopen(url, timeout=30, context=ssl_context) as response: ``` The destination is configurable without an allowlist: ```python parser.add_argument("--base-url", default="", help="覆盖环境变量 SCRM_BASE_URL") ... base_url = os.getenv("SCRM_BASE_URL", "https://open.wshoto.com") ``` ### Technical Analysis The APP KEY is sent to a caller-controlled `base_url`, with no HTTPS requirement or destination allowlist. An environment variable or command-line option can therefore redirect authentication and subsequent business API requests to an arbitrary host. The `SCRM_SKIP_SSL_VERIFY` option disables certificate validation. This exposes credentials and business data to active network interception. In addition, placing the APP KEY in the URL query string increases the chance that it will be captured by proxy logs, server access logs, monitoring systems, and URL diagnostics. Network transmission to the legitimate SCRM service is necessary for the Skill's declared functionality, but unrestricted destinations and optional TLS bypass exceed the minimum privilege required. ### Attack Path 1. An attacker modifies `SCRM_BASE_URL`, supplies `--base-url`, or influences the agent to do so. 2. T ...[truncated 643 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/claw_client.py:194
Finding

Generic proxy forwards arbitrary service, URI, and method values without local authorization

Content
View full analysis
dict[str, Any]: """调用 POST /claw/proxy/forward 代理转发。 接口可用性校验由后台完成,Skill 层不再重复验证。 返回数据中会标记 write_operation,用于区分读写操作。 """ write_op = self._is_write_operation(uri) body: dict[str, Any] = { "service_name": service_name, "uri": uri, "method": method, } if biz_params: body["biz_params"] = biz_params resp = self.client.post_json("/openapi/claw/proxy/forward", body) ``` ### Technical Analysis The local client forwards caller-controlled `service_name`, `uri`, `method`, and `biz_params` without requiring an exact match against the authenticated API catalog. The implementation explicitly delegates API availability validation to the remote backend. Role restrictions described in the agent runbook are behavioral instructions and are not enforced by the proxy client. Direct CLI use or prompt manipulation can therefore attempt endpoint combinations outside the flow selected by `list-apis`. The remote backend may independently reject unauthorized requests, but relying exclusively on that control does not satisfy defense in depth or local least privilege. ### Attack Path 1. An attacker directly invokes `call-api` or manipulates the agent into constructing arbitrary arguments. 2. The attacker supplies a service name, URI, method, and parameters not obtained from the approved API catalog. 3. The Skill forwards the request to `/openapi/claw/proxy/forward`. 4. If the backend proxy accepts the route, it invokes the downstream service with the victim's authenticated context. ### Impact Assessment Potential impact depends on backend enforcement. If an ...[truncated 274 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/claw_client.py:151
Finding

Write-operation detection fails open for unknown or differently named APIs

Content
View full analysis
bool: all_records = self._ensure_cache() for record in all_records: api_path = record.get("api_path", "") if api_path == uri: api_name = (record.get("api_name") or "").lower() return any(kw in api_name for kw in self._WRITE_KEYWORDS) if "{" in api_path: pattern = self._PATH_PARAM_RE.sub("[^/]+", api_path) if re.fullmatch(pattern, uri): api_name = (record.get("api_name") or "").lower() return any(kw in api_name for kw in self._WRITE_KEYWORDS) return False ``` ### Technical Analysis The code decides whether an API mutates state by searching a remotely supplied display name for a finite set of Chinese keywords. This is not an authoritative security property. A mutating endpoint with a synonym, English name, inaccurate metadata, or unknown path is classified as a read operation. The function also returns `False` when no catalog record matches, creating a fail-open default. The classification controls the documented confirmation and no-automatic-retry protections. Misclassification can therefore cause writes to be executed as if they were harmless queries. ### Attack Path 1. A mutating API has a name that does not contain one of the configured keywords, or an attacker supplies an unknown URI. 2. `_is_write_operation` returns `False`. 3. The agent or caller treats the operation as read-only. 4. The operation may be dispatched without final write confirmation. 5. If it times out or fails ambiguously, it may be retried and produce duplicate changes. ### Impact Assessment ...[truncated 255 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/file_utils.py:91
Finding

Image upload command can exfiltrate arbitrary local files

Content
View full analysis
dict[str, Any]: body, content_type = _build_multipart_body( path, file_field="file", extra_fields={} ) try: payload = client.post_multipart( UPLOAD_PATH, body, content_type=content_type ) except ApiError as exc: raise UploadError(str(exc), details=exc.details) from exc data = payload.get("data", {}) url = data.get("url") file_id = data.get("file_id") if not url: raise UploadError("本地图片上传成功,但无法从返回中提取 URL") if not file_id: raise UploadError("本地图片上传成功,但无法从返回中提取 file_id") return {"url": url, "file_id": file_id} def _build_multipart_body( path: Path, *, file_field: str, extra_fields: dict[str, str], ) -> tuple[bytes, str]: ... content_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream" file_bytes = path.read_bytes() ... ``` The CLI only verifies that the path refers to a file: ```python path = Path(args.path).expanduser().resolve() if not path.exists() or not path.is_file(): raise ValidationError(f"文件不存在:{path}") return upload_image(path, client=client) ``` ### Technical Analysis Despite being described as an image-upload function, the implementation accepts any readable local file. MIME type is inferred from the filename and is not validated against the actual content. There is no size limit, image decoding, safe-directory policy, symlink restriction, or explicit user confirmation tied to the resolved file. An attacker who can influence the path passed by an agent can cause sensitive files to be read and transmitted to the remote upload endpoint. ### Attack Path 1. An attacker instructs or manipulates the agent to upload a sensitive local path. 2. The path re ...[truncated 559 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/raw_fetcher.py:41
Finding

Remote-document allowlist is bypassable through redirects

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (52)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The package reportedly fetches arbitrary or semi-controlled remote text content from open.wshoto.com and returns raw content/metadata, which is outside the declared CRM-query scope. Unnecessary remote content retrieval can be abused for data exfiltration, prompt/context injection, or unintended processing of untrusted documents.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger is explicitly broad enough to activate even when the user does not mention the product, API, or clear intent to use this integration. In a skill with access to customer records, chat archives, credentials, and local environment checks, overbroad triggering raises the likelihood of inappropriate activation, unnecessary data access, or execution of side-effecting setup logic in unrelated conversations.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · install.sh (reported line 59)May include surrounding context.

sh
# 清理 SKILL.md 软链接(如果是本脚本创建的)
  if [[ -L "${SCRIPT_DIR}/SKILL.md" ]]; then
    rm "${SCRIPT_DIR}/SKILL.md"
    echo "✓ 已删除 SKILL.md 软链接"
  fi
}

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/api_client.py (reported line 23)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/api_client.py (reported line 67)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/get_access_token.py (reported line 2)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/get_access_token.py (reported line 25)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/get_access_token.py (reported line 33)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/get_access_token.py (reported line 115)May include surrounding context.

python
#!/usr/bin/env python3
"""获取 SCRM Personal Access Token,包含自动缓存管理。"""
import argparse
import json
import os

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

On successful execution, the non-raw output includes the access_token value in the returned data structure, which risks exposing a live credential to logs, calling frameworks, transcripts, or downstream tooling. In an agent/skill context, structured outputs are often captured automatically, making credential leakage more dangerous than in a standalone local script.

Content

Scanner excerpt · scripts/get_access_token.py (reported line 146)May include surrounding context.

python
"user_id": cached.get("user_id") if cached else None,
                "cached": not args.force_refresh and cached is not None,
            }
            output_success("get_access_token", data, "获取 Access Token 成功")
    except ApiError as e:
        if args.raw:
            sys.exit(1)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README explicitly instructs users to persist a personal access token via set-app-key into Shell configuration files, but does not warn that this creates long-lived local credential exposure. If the host is shared, backed up insecurely, or the shell profile is readable by other processes or users, the token could be recovered and used to access SCRM data and perform authorized API actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The README documents cache files containing token, user_id, and identity information, plus logging to a local file, without warning about confidentiality or retention risks. On multi-user systems or compromised endpoints, these artifacts can leak authentication material and sensitive user metadata, enabling unauthorized access or facilitating further targeting.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill text mandates reading and following an external runbook with commands such as environment and identity checks, and the broader package evidently uses shell, filesystem, network, and environment access, yet SKILL.md declares no explicit tool scope or permission boundaries. That creates a confused-deputy risk: an agent may invoke powerful capabilities without transparent least-privilege constraints or user-visible authorization limits.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The scope statement is broad and lacks clear exclusions or trigger constraints, encouraging activation across many customer-operations scenarios without precise boundaries. Because the surrounding skill appears to involve authentication, local state, and networked business data, vague scoping materially increases accidental overreach and privacy risk.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The script's comments and all user-facing messages are in Chinese, including the usage text and operational notices. For a general install/uninstall script, this imposes a specific language on users without opt-in or a clearly documented locale-specific justification in the file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Line L007 instructs the AI to '默认使用业务语言' when replying to ordinary users, establishing a default language/style policy without asking for the user's preference. The file does not offer a language or locale choice or describe this as a justified region-specific constraint, so it fits the natural-language policy violation criteria.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The examples use very generic requests such as '帮我查一下', '帮我看看', and '帮我创建' for common CRM-style tasks without anchoring them to explicit WeCom/SCRM context. In an agent-routing system, this can cause over-triggering on ordinary business requests, leading the skill to access or act on customer-management functions when the user did not clearly intend this integration, increasing the risk of unintended data exposure or unauthorized actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The documentation explicitly describes automatic local file upload and remote file download behavior, but it does not require user confirmation, origin restrictions, path safety checks, or warnings about data transfer and filesystem side effects. In an agent context, this can cause unintended exfiltration of local files or unsafe retrieval of remote content simply from user-supplied paths or URLs, which is especially risky because the skill is meant to trigger broadly in business workflows.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The guide exclusively uses Chinese for all user-facing instructions and examples, and it does not mention that the skill is limited to Chinese-speaking users or provide an opt-in language choice. This can violate a language/locale policy when skills are expected to avoid forcing a specific language without user consent.

Content

No source excerpt is available for this finding.